Commit 7e99dfc3 authored by Alain Takoudjou's avatar Alain Takoudjou

add pluging for run instance script at partition pre-destroy phase

This pluging can be used a add a script which wipe data before the partition is removed
The script run after all partition services are stopped. The script is ran by supervisord
then slapos node report will wait until it finish before destroy the partition.

/reviewed-on nexedi/slapos.core!22
parent 8338ec42
...@@ -453,6 +453,17 @@ class Partition(object): ...@@ -453,6 +453,17 @@ class Partition(object):
'USER': pwd.getpwuid(uid).pw_name, 'USER': pwd.getpwuid(uid).pw_name,
} }
def addServiceToCustomGroup(self, group_id, runner_list, path):
"""Add new services to supervisord that belong to specific group"""
group_partition_template = pkg_resources.resource_stream(__name__,
'templates/group_partition_supervisord.conf.in').read()
self.supervisor_configuration_groups += group_partition_template % {
'instance_id': group_id,
'program_list': ','.join(['_'.join([group_id, runner])
for runner in runner_list])
}
return self.addServiceToGroup(group_id, runner_list, path)
def updateSymlink(self, sr_symlink, software_path): def updateSymlink(self, sr_symlink, software_path):
if os.path.lexists(sr_symlink): if os.path.lexists(sr_symlink):
if not os.path.islink(sr_symlink): if not os.path.islink(sr_symlink):
...@@ -589,7 +600,7 @@ class Partition(object): ...@@ -589,7 +600,7 @@ class Partition(object):
self.generateSupervisorConfigurationFile() self.generateSupervisorConfigurationFile()
self.createRetentionLockDelay() self.createRetentionLockDelay()
def generateSupervisorConfigurationFile(self): def generateSupervisorConfiguration(self):
""" """
Generates supervisord configuration file from template. Generates supervisord configuration file from template.
...@@ -600,6 +611,8 @@ class Partition(object): ...@@ -600,6 +611,8 @@ class Partition(object):
""" """
runner_list = [] runner_list = []
service_list = [] service_list = []
self.partition_supervisor_configuration = ""
self.supervisor_configuration_groups = ""
if os.path.exists(self.run_path): if os.path.exists(self.run_path):
if os.path.isdir(self.run_path): if os.path.isdir(self.run_path):
runner_list = os.listdir(self.run_path) runner_list = os.listdir(self.run_path)
...@@ -615,7 +628,7 @@ class Partition(object): ...@@ -615,7 +628,7 @@ class Partition(object):
partition_id = self.computer_partition.getId() partition_id = self.computer_partition.getId()
group_partition_template = pkg_resources.resource_stream(__name__, group_partition_template = pkg_resources.resource_stream(__name__,
'templates/group_partition_supervisord.conf.in').read() 'templates/group_partition_supervisord.conf.in').read()
self.partition_supervisor_configuration = group_partition_template % { self.supervisor_configuration_groups = group_partition_template % {
'instance_id': partition_id, 'instance_id': partition_id,
'program_list': ','.join(['_'.join([partition_id, runner]) 'program_list': ','.join(['_'.join([partition_id, runner])
for runner in runner_list + service_list]) for runner in runner_list + service_list])
...@@ -624,10 +637,25 @@ class Partition(object): ...@@ -624,10 +637,25 @@ class Partition(object):
self.addServiceToGroup(partition_id, runner_list, self.run_path) self.addServiceToGroup(partition_id, runner_list, self.run_path)
self.addServiceToGroup(partition_id, service_list, self.service_path, self.addServiceToGroup(partition_id, service_list, self.service_path,
extension=WATCHDOG_MARK) extension=WATCHDOG_MARK)
def writeSupervisorConfigurationFile(self):
"""
Write supervisord configuration file and update supervisord
"""
if self.supervisor_configuration_groups and \
self.partition_supervisor_configuration:
updateFile(self.supervisord_partition_configuration_path, updateFile(self.supervisord_partition_configuration_path,
self.supervisor_configuration_groups +
self.partition_supervisor_configuration) self.partition_supervisor_configuration)
self.updateSupervisor() self.updateSupervisor()
def generateSupervisorConfigurationFile(self):
"""
update supervisord with new processes
"""
self.generateSupervisorConfiguration()
self.writeSupervisorConfigurationFile()
def start(self): def start(self):
"""Asks supervisord to start the instance. If this instance is not """Asks supervisord to start the instance. If this instance is not
installed, we install it. installed, we install it.
...@@ -720,6 +748,19 @@ class Partition(object): ...@@ -720,6 +748,19 @@ class Partition(object):
return True return True
def checkProcessesFromStateList(self, process_list, state_list):
"""Asks supervisord to check if one of the processes are in the state_list."""
supervisor = self.getSupervisorRPC()
for process in process_list:
try:
info = supervisor.getProcessInfo(process)
if info['statename'] in state_list:
return True
except xmlrpclib.Fault as exc:
self.logger.debug("BAD process name: %r" % process)
continue
return False
def cleanupFolder(self, folder_path): def cleanupFolder(self, folder_path):
"""Delete all files and folders in a specified directory """Delete all files and folders in a specified directory
""" """
......
...@@ -80,6 +80,7 @@ PROMISE_TIMEOUT = 3 ...@@ -80,6 +80,7 @@ PROMISE_TIMEOUT = 3
COMPUTER_PARTITION_TIMESTAMP_FILENAME = '.timestamp' COMPUTER_PARTITION_TIMESTAMP_FILENAME = '.timestamp'
COMPUTER_PARTITION_LATEST_BANG_TIMESTAMP_FILENAME = '.slapos_latest_bang_timestamp' COMPUTER_PARTITION_LATEST_BANG_TIMESTAMP_FILENAME = '.slapos_latest_bang_timestamp'
COMPUTER_PARTITION_INSTALL_ERROR_FILENAME = '.slapgrid-%s-error.log' COMPUTER_PARTITION_INSTALL_ERROR_FILENAME = '.slapgrid-%s-error.log'
COMPUTER_PARTITION_WAIT_LIST_FILENAME = '.slapos-wait-services'
# XXX hardcoded watchdog_path # XXX hardcoded watchdog_path
WATCHDOG_PATH = '/opt/slapos/bin/slapos-watchdog' WATCHDOG_PATH = '/opt/slapos/bin/slapos-watchdog'
...@@ -1266,6 +1267,18 @@ stderr_logfile_backups=1 ...@@ -1266,6 +1267,18 @@ stderr_logfile_backups=1
return SLAPGRID_PROMISE_FAIL return SLAPGRID_PROMISE_FAIL
return SLAPGRID_SUCCESS return SLAPGRID_SUCCESS
def _checkWaitProcessList(self, partition, state_list):
wait_file = os.path.join(partition.instance_path,
COMPUTER_PARTITION_WAIT_LIST_FILENAME)
if os.path.exists(wait_file) and os.path.isfile(wait_file):
with open(wait_file) as wait_f:
processes_list = [name.strip() for name in wait_f.readlines() if name]
# return True if one of process in the list is running
return partition.checkProcessesFromStateList(processes_list,
state_list)
return False
def validateXML(self, to_be_validated, xsd_model): def validateXML(self, to_be_validated, xsd_model):
"""Validates a given xml file""" """Validates a given xml file"""
#We retrieve the xsd model #We retrieve the xsd model
...@@ -1572,10 +1585,19 @@ stderr_logfile_backups=1 ...@@ -1572,10 +1585,19 @@ stderr_logfile_backups=1
raise raise
except Exception: except Exception:
pass pass
# let managers update current partition
for manager in self._manager_list:
manager.report(local_partition)
if computer_partition.getId() in report_usage_issue_cp_list: if computer_partition.getId() in report_usage_issue_cp_list:
self.logger.info('Ignoring destruction of %r, as no report usage was sent' % self.logger.info('Ignoring destruction of %r, as no report usage was sent' %
computer_partition.getId()) computer_partition.getId())
continue continue
if self._checkWaitProcessList(local_partition,
state_list=['RUNNING', 'STARTING']):
self.logger.info('There are running processes into the partition,' \
' wait until they finish...')
continue
destroyed = local_partition.destroy() destroyed = local_partition.destroy()
except (SystemExit, KeyboardInterrupt): except (SystemExit, KeyboardInterrupt):
computer_partition.error(traceback.format_exc(), logger=self.logger) computer_partition.error(traceback.format_exc(), logger=self.logger)
......
...@@ -29,3 +29,8 @@ class IManager(Interface): ...@@ -29,3 +29,8 @@ class IManager(Interface):
:param partition: slapos.grid.SlapObject.Partition, currently processed partition :param partition: slapos.grid.SlapObject.Partition, currently processed partition
""" """
def report(partition):
"""Method called at `slapos node report` phase.
:param partition: slapos.grid.SlapObject.Partition, currently processed partition
"""
# coding: utf-8
import logging
import os
import sys
import subprocess
from zope import interface as zope_interface
from slapos.manager import interface
from slapos.grid.slapgrid import COMPUTER_PARTITION_WAIT_LIST_FILENAME
logger = logging.getLogger(__name__)
WIPE_WRAPPER_BASE_PATH = "var/run/slapos/pre-destroy/"
class Manager(object):
"""Manager is called in every step of preparation of the computer."""
zope_interface.implements(interface.IManager)
def __init__(self, config):
"""Manager needs to know config for its functioning.
"""
pass
def format(self, computer):
"""Method called at `slapos node format` phase.
"""
pass
def software(self, software):
"""Method called at `slapos node software` phase.
"""
pass
def instance(self, partition):
"""Method called at `slapos node instance` phase.
"""
pass
def report(self, partition):
"""Method called at `slapos node report` phase."""
partition.createRetentionLockDate()
if not partition.checkRetentionIsAuthorized():
return
wait_filepath = os.path.join(partition.instance_path,
COMPUTER_PARTITION_WAIT_LIST_FILENAME)
wipe_base_folder = os.path.join(partition.instance_path,
WIPE_WRAPPER_BASE_PATH)
if not os.path.exists(wipe_base_folder):
return
wipe_wrapper_list = [f for f in os.listdir(wipe_base_folder)
if os.path.isfile(os.path.join(wipe_base_folder, f))]
if len(wipe_wrapper_list) > 0:
group_name = partition.partition_id + '-' + "destroy"
logger.info("Adding pre-destroy scripts to supervisord...")
partition.generateSupervisorConfiguration()
partition.addServiceToCustomGroup(group_name,
wipe_wrapper_list,
wipe_base_folder)
partition.writeSupervisorConfigurationFile()
# check the state of all process, if the process is not started yes, start it
supervisord = partition.getSupervisorRPC()
process_list_string = ""
for name in wipe_wrapper_list:
process_name = group_name + ':' + name
process_list_string += process_name + '\n'
status = supervisord.getProcessInfo(process_name)
if status['start'] == 0:
# process is not started yet
logger.info("Starting pre-destroy process %r..." % name)
supervisord.startProcess(process_name, False)
# ask to slapgrid to check theses scripts before destroy partition
with open(wait_filepath, 'w') as f:
f.write(process_list_string)
...@@ -2414,3 +2414,70 @@ class TestSlapgridCPWithTransaction(MasterMixin, unittest.TestCase): ...@@ -2414,3 +2414,70 @@ class TestSlapgridCPWithTransaction(MasterMixin, unittest.TestCase):
self.assertInstanceDirectoryListEqual(['0']) self.assertInstanceDirectoryListEqual(['0'])
self.assertFalse(os.path.exists(request_list_file)) self.assertFalse(os.path.exists(request_list_file))
class TestSlapgridCPWithPreDeleteScript(MasterMixin, unittest.TestCase):
def test_one_partition_pre_destroy_service(self):
from slapos import manager as slapmanager
from slapos.manager.predestroy import WIPE_WRAPPER_BASE_PATH
computer = ComputerForTest(self.software_root, self.instance_root)
with httmock.HTTMock(computer.request_handler):
partition = computer.instance_list[0]
pre_delete_dir = os.path.join(partition.partition_path, WIPE_WRAPPER_BASE_PATH)
pre_delete_script = os.path.join(pre_delete_dir, 'slapos_pre_delete')
partition.requested_state = 'started'
partition.software.setBuildout(WRAPPER_CONTENT)
self.assertEqual(self.grid.processComputerPartitionList(), slapgrid.SLAPGRID_SUCCESS)
os.makedirs(pre_delete_dir, 0o700)
with open(pre_delete_script, 'w') as f:
f.write("""#!/bin/sh
echo "Running script to wipe this partition..."
for i in {1..3}
do
echo "sleeping for 1s..."
sleep 1
done
echo "finished wipe disk."
exit 0
""")
os.chmod(pre_delete_script, 0754)
self.assertInstanceDirectoryListEqual(['0'])
self.assertItemsEqual(os.listdir(partition.partition_path),
['.slapgrid', '.0_wrapper.log', 'buildout.cfg', 'var',
'etc', 'software_release', 'worked', '.slapos-retention-lock-delay'])
wrapper_log = os.path.join(partition.partition_path, '.0_wrapper.log')
self.assertLogContent(wrapper_log, 'Working')
self.assertItemsEqual(os.listdir(self.software_root), [partition.software.software_hash])
self.assertEqual(computer.sequence,
['/getFullComputerInformation', '/availableComputerPartition',
'/startedComputerPartition'])
self.assertEqual(partition.state, 'started')
partition.requested_state = 'stopped'
self.assertEqual(self.launchSlapgrid(), slapgrid.SLAPGRID_SUCCESS)
self.assertEqual(partition.state, 'stopped')
manager_list = slapmanager.from_config({'manager_list': 'predestroy'})
self.grid._manager_list = manager_list
partition.requested_state = 'destroyed'
self.assertEqual(self.grid.agregateAndSendUsage(), slapgrid.SLAPGRID_SUCCESS)
# Assert partition directory is not destroyed (pre-destroy is running)
self.assertInstanceDirectoryListEqual(['0'])
self.assertItemsEqual(os.listdir(partition.partition_path),
['.slapgrid', '.0_wrapper.log', 'buildout.cfg', 'var',
'etc', 'software_release', 'worked', '.slapos-retention-lock-delay',
'.0-destroy_slapos_pre_delete.log', '.slapos-wait-services',
'.slapos-request-transaction-0'])
self.assertItemsEqual(os.listdir(self.software_root),
[partition.software.software_hash])
# wait until the pre-destroy script is finished
time.sleep(5)
self.assertEqual(self.grid.agregateAndSendUsage(), slapgrid.SLAPGRID_SUCCESS)
# Assert partition directory is empty
self.assertInstanceDirectoryListEqual(['0'])
self.assertItemsEqual(os.listdir(partition.partition_path), [])
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