Commit c49161fb authored by Alain Takoudjou's avatar Alain Takoudjou

Move slapgrid checkpromise method to utils so it can be reused

rename checkPromises to checkPromiseList, and move the method to grid/utils so it can be imported by monitor to check promises.


`raise_on_failure` will be used to check all promises without raise. All promises result will be returned.

`profile` True/False will log promise execution time.

/reviewed-on !35
parent 79626d6d
...@@ -60,7 +60,8 @@ from slapos.grid.svcbackend import (launchSupervisord, ...@@ -60,7 +60,8 @@ from slapos.grid.svcbackend import (launchSupervisord,
createSupervisordConfiguration, createSupervisordConfiguration,
_getSupervisordConfigurationDirectory, _getSupervisordConfigurationDirectory,
_getSupervisordSocketPath) _getSupervisordSocketPath)
from slapos.grid.utils import (md5digest, dropPrivileges, SlapPopen, updateFile) from slapos.grid.utils import (md5digest, dropPrivileges, SlapPopen, updateFile,
checkPromiseList, PromiseError)
from slapos.human import human2bytes from slapos.human import human2bytes
import slapos.slap import slapos.slap
from netaddr import valid_ipv4, valid_ipv6 from netaddr import valid_ipv4, valid_ipv6
...@@ -295,9 +296,6 @@ class Slapgrid(object): ...@@ -295,9 +296,6 @@ class Slapgrid(object):
server and pushes usage information to master server. server and pushes usage information to master server.
""" """
class PromiseError(Exception):
pass
def __init__(self, def __init__(self,
software_root, software_root,
instance_root, instance_root,
...@@ -619,7 +617,7 @@ stderr_logfile_backups=1 ...@@ -619,7 +617,7 @@ stderr_logfile_backups=1
return SLAPGRID_FAIL return SLAPGRID_FAIL
return SLAPGRID_SUCCESS return SLAPGRID_SUCCESS
def _checkPromises(self, computer_partition): def _checkPromiseList(self, computer_partition):
self.logger.info("Checking promises...") self.logger.info("Checking promises...")
instance_path = os.path.join(self.instance_root, computer_partition.getId()) instance_path = os.path.join(self.instance_root, computer_partition.getId())
...@@ -629,54 +627,11 @@ stderr_logfile_backups=1 ...@@ -629,54 +627,11 @@ stderr_logfile_backups=1
#stat sys call to get statistics informations #stat sys call to get statistics informations
uid = stat_info.st_uid uid = stat_info.st_uid
gid = stat_info.st_gid gid = stat_info.st_gid
promise_present = False
# Get the list of promises
promise_dir = os.path.join(instance_path, 'etc', 'promise') promise_dir = os.path.join(instance_path, 'etc', 'promise')
if os.path.exists(promise_dir) and os.path.isdir(promise_dir):
# Check whether every promise is kept
for promise in os.listdir(promise_dir):
promise_present = True
command = [os.path.join(promise_dir, promise)]
promise = os.path.basename(command[0])
self.logger.info("Checking promise '%s'.", promise)
process_handler = subprocess.Popen(command,
preexec_fn=lambda: dropPrivileges(uid, gid, logger=self.logger),
cwd=instance_path,
env=None if sys.platform == 'cygwin' else {},
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
process_handler.stdin.flush()
process_handler.stdin.close()
process_handler.stdin = None
# Check if the promise finished every tenth of second,
# but timeout after promise_timeout.
sleep_time = 0.1
increment_limit = int(self.promise_timeout / sleep_time)
for current_increment in range(0, increment_limit):
if process_handler.poll() is None:
time.sleep(sleep_time)
continue
if process_handler.poll() == 0:
# Success!
break
else:
stderr = process_handler.communicate()[1]
if stderr is None:
stderr = "No error output from '%s'." % promise
else:
stderr = "Promise '%s':" % promise + stderr
raise Slapgrid.PromiseError(stderr)
else:
process_handler.terminate()
raise Slapgrid.PromiseError("The promise '%s' timed out" % promise)
if not promise_present: if not checkPromiseList(promise_dir, self.promise_timeout, uid=uid, gid=gid,
cwd=instance_path, logger=self.logger, profile=True,
raise_on_failure=True):
self.logger.info("No promise.") self.logger.info("No promise.")
def _endInstallationTransaction(self, computer_partition): def _endInstallationTransaction(self, computer_partition):
...@@ -1091,7 +1046,7 @@ stderr_logfile_backups=1 ...@@ -1091,7 +1046,7 @@ stderr_logfile_backups=1
if self.firewall_conf: if self.firewall_conf:
self._setupComputerPartitionFirewall(computer_partition, self._setupComputerPartitionFirewall(computer_partition,
partition_ip_list) partition_ip_list)
self._checkPromises(computer_partition) self._checkPromiseList(computer_partition)
computer_partition.started() computer_partition.started()
self._endInstallationTransaction(computer_partition) self._endInstallationTransaction(computer_partition)
elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE: elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE:
...@@ -1235,7 +1190,7 @@ stderr_logfile_backups=1 ...@@ -1235,7 +1190,7 @@ stderr_logfile_backups=1
computer_partition.error(traceback.format_exc(), logger=self.logger) computer_partition.error(traceback.format_exc(), logger=self.logger)
raise raise
except Slapgrid.PromiseError as exc: except PromiseError as exc:
clean_run_promise = False clean_run_promise = False
try: try:
self.logger.error(exc) self.logger.error(exc)
......
...@@ -36,6 +36,9 @@ import pwd ...@@ -36,6 +36,9 @@ import pwd
import stat import stat
import subprocess import subprocess
import sys import sys
import logging
import time
from datetime import datetime
from slapos.grid.exception import BuildoutFailedError, WrongPermissionError from slapos.grid.exception import BuildoutFailedError, WrongPermissionError
...@@ -87,6 +90,9 @@ LOCALE_ENVIRONMENT_REMOVE_LIST = [ ...@@ -87,6 +90,9 @@ LOCALE_ENVIRONMENT_REMOVE_LIST = [
] ]
class PromiseError(Exception):
pass
class SlapPopen(subprocess.Popen): class SlapPopen(subprocess.Popen):
""" """
Almost normal subprocess with greedish features and logging. Almost normal subprocess with greedish features and logging.
...@@ -358,3 +364,95 @@ def createPrivateDirectory(path): ...@@ -358,3 +364,95 @@ def createPrivateDirectory(path):
raise WrongPermissionError('Wrong permissions in %s: ' raise WrongPermissionError('Wrong permissions in %s: '
'is 0%o, should be 0700' 'is 0%o, should be 0700'
% (path, permission)) % (path, permission))
def checkPromiseList(promise_dir, promise_timeout, uid=None, gid=None, cwd=None,
logger=None, profile=False, raise_on_failure=True):
"""
Check a promise list and return the result or raise in case of failure
if `raise_on_failure` is set to True
When `profile` is set to True, log each promise execution duration.
"""
if logger is None:
logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
promise_result_list = []
if os.path.exists(promise_dir) and os.path.isdir(promise_dir):
kw = {}
if uid is not None and gid is not None:
kw["preexec_fn"] = lambda: dropPrivileges(uid, gid, logger=logger)
if cwd is not None:
kw["cwd"] = cwd
for promise in os.listdir(promise_dir):
command = [os.path.join(promise_dir, promise)]
promise = os.path.basename(command[0])
logger.info("Checking promise '%s'.", promise)
if not os.path.isfile(command[0]) or not os.access(command[0], os.X_OK):
# Not executable file
logger.warning("Promise script '%s' is not executable.", promise)
continue
result_dict = {
"returncode": -1,
"title": promise,
"start-date" : datetime.utcnow(),
"execution-time": 0,
"message": ""
}
process_handler = subprocess.Popen(command,
env=None if sys.platform == 'cygwin' else {},
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
**kw)
process_handler.stdin.flush()
process_handler.stdin.close()
process_handler.stdin = None
# Check if the promise finished every tenth of second,
# but timeout after promise_timeout.
sleep_time = 0.1
increment_limit = int(promise_timeout / sleep_time)
for current_increment in range(0, increment_limit):
if process_handler.poll() is None:
time.sleep(sleep_time)
continue
result_dict["execution-time"] = current_increment * sleep_time
result_dict["returncode"] = process_handler.poll()
if result_dict["returncode"] == 0:
# Success!
result_dict["message"] = process_handler.communicate()[0]
else:
stdout, stderr = process_handler.communicate()
if raise_on_failure:
if stderr is None:
stderr = "No error output from '%s'." % promise
else:
stderr = "Promise '%s':" % promise + stderr
raise PromiseError(stderr)
if not stderr:
result_dict["message"] = stdout or ""
else:
result_dict["message"] = stderr
break
else:
process_handler.terminate()
if raise_on_failure:
raise PromiseError("The promise '%s' timed out" % promise)
message = process_handler.stderr.read()
if message is None:
message = process_handler.stdout.read() or ""
message += '\nPROMISE TIMED OUT AFTER %s SECONDS' % promise_timeout
result_dict["message"] = message
result_dict["execution-time"] = current_increment * sleep_time
promise_result_list.append(result_dict)
if profile:
logger.info("Finished promise %r in %s second(s)." % (
promise, result_dict["execution-time"]))
return promise_result_list
##############################################################################
#
# Copyright (c) 2013 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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 3
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import os
import slapos.grid.utils
import tempfile
import unittest
class TestGridUtils(unittest.TestCase):
def test_check_promise_success(self):
"""
check that checkPromiseList works well
"""
script = """#!/bin/sh
echo "%(name)s"
exit %(code)s
"""
root_directory = tempfile.mkdtemp()
promise_1 = "first_promise"
promise_2 = "second_promise"
promise_timeout = 10
with open(os.path.join(root_directory, promise_1), 'w') as f:
f.write(script % {'name': promise_1, 'code': 0})
with open(os.path.join(root_directory, promise_2), 'w') as f:
f.write(script % {'name': promise_2, 'code': 0})
for file in os.listdir(root_directory):
if 'promise' in file:
os.chmod(os.path.join(root_directory, file), 0755)
result_list = []
try:
result_list = slapos.grid.utils.checkPromiseList(
root_directory,
promise_timeout,
profile=True,
raise_on_failure=True,
logger=None)
except slapos.grid.utils.PromiseError:
self.fail("Unexpected raise of PromiseError in 'checkPromiseList()'")
for result in result_list:
self.assertEquals(result['returncode'], 0)
self.assertTrue(result['message'].strip() in [promise_1, promise_2])
def test_check_promise_failure(self):
"""
check that checkPromiseList works well
"""
script = """#!/bin/sh
echo "%(name)s"
exit %(code)s
"""
root_directory = tempfile.mkdtemp()
promise_1 = "first_promise"
promise_2 = "second_promise_fail"
promise_timeout = 10
with open(os.path.join(root_directory, promise_1), 'w') as f:
f.write(script % {'name': promise_1, 'code': 0})
with open(os.path.join(root_directory, promise_2), 'w') as f:
f.write(script % {'name': promise_2, 'code': 1})
for file in os.listdir(root_directory):
if 'promise' in file:
os.chmod(os.path.join(root_directory, file), 0755)
with self.assertRaises(slapos.grid.utils.PromiseError):
slapos.grid.utils.checkPromiseList(
root_directory,
promise_timeout,
profile=True,
raise_on_failure=True,
logger=None)
def test_check_promise_no_raise(self):
"""
check that checkPromiseList works well
"""
script = """#!/bin/sh
echo "%(name)s"
exit %(code)s
"""
root_directory = tempfile.mkdtemp()
promise_1 = "first_promise"
promise_2 = "second_promise"
promise_3 = "third_promise"
promise_4 = "fourth_promise_fail"
promise_timeout = 10
with open(os.path.join(root_directory, promise_1), 'w') as f:
f.write(script % {'name': promise_1, 'code': 0})
with open(os.path.join(root_directory, promise_2), 'w') as f:
f.write(script % {'name': promise_2, 'code': 0})
with open(os.path.join(root_directory, promise_3), 'w') as f:
f.write(script % {'name': promise_3, 'code': 0})
with open(os.path.join(root_directory, promise_4), 'w') as f:
f.write(script % {'name': promise_4, 'code': 1})
for file in os.listdir(root_directory):
if 'promise' in file:
os.chmod(os.path.join(root_directory, file), 0755)
result_list = []
try:
result_list = slapos.grid.utils.checkPromiseList(
root_directory,
promise_timeout,
profile=True,
raise_on_failure=False,
logger=None)
except slapos.grid.utils.PromiseError:
self.fail("Unexpected raise of PromiseError in 'checkPromiseList()'")
for result in result_list:
self.assertTrue(result['message'].strip() in [promise_1, promise_2, promise_3, promise_4])
if result['title'] == promise_4:
self.assertEquals(result['returncode'], 1)
else:
self.assertEquals(result['returncode'], 0)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment