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 nexedi/slapos.core!35
parent 79626d6d
......@@ -60,7 +60,8 @@ from slapos.grid.svcbackend import (launchSupervisord,
createSupervisordConfiguration,
_getSupervisordConfigurationDirectory,
_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
import slapos.slap
from netaddr import valid_ipv4, valid_ipv6
......@@ -295,9 +296,6 @@ class Slapgrid(object):
server and pushes usage information to master server.
"""
class PromiseError(Exception):
pass
def __init__(self,
software_root,
instance_root,
......@@ -619,7 +617,7 @@ stderr_logfile_backups=1
return SLAPGRID_FAIL
return SLAPGRID_SUCCESS
def _checkPromises(self, computer_partition):
def _checkPromiseList(self, computer_partition):
self.logger.info("Checking promises...")
instance_path = os.path.join(self.instance_root, computer_partition.getId())
......@@ -629,54 +627,11 @@ stderr_logfile_backups=1
#stat sys call to get statistics informations
uid = stat_info.st_uid
gid = stat_info.st_gid
promise_present = False
# Get the list of promises
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.")
def _endInstallationTransaction(self, computer_partition):
......@@ -1091,7 +1046,7 @@ stderr_logfile_backups=1
if self.firewall_conf:
self._setupComputerPartitionFirewall(computer_partition,
partition_ip_list)
self._checkPromises(computer_partition)
self._checkPromiseList(computer_partition)
computer_partition.started()
self._endInstallationTransaction(computer_partition)
elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE:
......@@ -1235,7 +1190,7 @@ stderr_logfile_backups=1
computer_partition.error(traceback.format_exc(), logger=self.logger)
raise
except Slapgrid.PromiseError as exc:
except PromiseError as exc:
clean_run_promise = False
try:
self.logger.error(exc)
......
......@@ -36,6 +36,9 @@ import pwd
import stat
import subprocess
import sys
import logging
import time
from datetime import datetime
from slapos.grid.exception import BuildoutFailedError, WrongPermissionError
......@@ -87,6 +90,9 @@ LOCALE_ENVIRONMENT_REMOVE_LIST = [
]
class PromiseError(Exception):
pass
class SlapPopen(subprocess.Popen):
"""
Almost normal subprocess with greedish features and logging.
......@@ -358,3 +364,95 @@ def createPrivateDirectory(path):
raise WrongPermissionError('Wrong permissions in %s: '
'is 0%o, should be 0700'
% (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