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):
'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):
if os.path.lexists(sr_symlink):
if not os.path.islink(sr_symlink):
......@@ -589,7 +600,7 @@ class Partition(object):
self.generateSupervisorConfigurationFile()
self.createRetentionLockDelay()
def generateSupervisorConfigurationFile(self):
def generateSupervisorConfiguration(self):
"""
Generates supervisord configuration file from template.
......@@ -600,6 +611,8 @@ class Partition(object):
"""
runner_list = []
service_list = []
self.partition_supervisor_configuration = ""
self.supervisor_configuration_groups = ""
if os.path.exists(self.run_path):
if os.path.isdir(self.run_path):
runner_list = os.listdir(self.run_path)
......@@ -615,7 +628,7 @@ class Partition(object):
partition_id = self.computer_partition.getId()
group_partition_template = pkg_resources.resource_stream(__name__,
'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,
'program_list': ','.join(['_'.join([partition_id, runner])
for runner in runner_list + service_list])
......@@ -624,10 +637,25 @@ class Partition(object):
self.addServiceToGroup(partition_id, runner_list, self.run_path)
self.addServiceToGroup(partition_id, service_list, self.service_path,
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,
self.supervisor_configuration_groups +
self.partition_supervisor_configuration)
self.updateSupervisor()
def generateSupervisorConfigurationFile(self):
"""
update supervisord with new processes
"""
self.generateSupervisorConfiguration()
self.writeSupervisorConfigurationFile()
def start(self):
"""Asks supervisord to start the instance. If this instance is not
installed, we install it.
......@@ -720,6 +748,19 @@ class Partition(object):
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):
"""Delete all files and folders in a specified directory
"""
......
......@@ -80,6 +80,7 @@ PROMISE_TIMEOUT = 3
COMPUTER_PARTITION_TIMESTAMP_FILENAME = '.timestamp'
COMPUTER_PARTITION_LATEST_BANG_TIMESTAMP_FILENAME = '.slapos_latest_bang_timestamp'
COMPUTER_PARTITION_INSTALL_ERROR_FILENAME = '.slapgrid-%s-error.log'
COMPUTER_PARTITION_WAIT_LIST_FILENAME = '.slapos-wait-services'
# XXX hardcoded watchdog_path
WATCHDOG_PATH = '/opt/slapos/bin/slapos-watchdog'
......@@ -1266,6 +1267,18 @@ stderr_logfile_backups=1
return SLAPGRID_PROMISE_FAIL
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):
"""Validates a given xml file"""
#We retrieve the xsd model
......@@ -1572,10 +1585,19 @@ stderr_logfile_backups=1
raise
except Exception:
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:
self.logger.info('Ignoring destruction of %r, as no report usage was sent' %
computer_partition.getId())
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()
except (SystemExit, KeyboardInterrupt):
computer_partition.error(traceback.format_exc(), logger=self.logger)
......
......@@ -29,3 +29,8 @@ class IManager(Interface):
: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):
self.assertInstanceDirectoryListEqual(['0'])
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