Commit a58c14b0 authored by Jérome Perrin's avatar Jérome Perrin

proxy: support partitions destruction

So far implementation of slapos proxy was incomplete regarding partition destructions:
A partition requested as destroyed was properly destroyed "physically", files were
removed from filesystem, services were removed from supervisor, but the partition was
still marked as busy in proxy database, so it was never re-used in subsequent requests.

To solve this, implement the destroyedComputerPartition endpoint to mark the partition
as free.

This is straightworfard for root partitions, but the cases of child partitions was a bit
more tricky, especially because software releases usually does not implement properly
the chain of deletion (it seems this is not properly implemted with softwaretype, only
with switch_softwaretype) and generally because we don't want to leave orpheans partitions.

In the case of ERP5 implementation of SlapOS master we have an alarm which garbage
collect in these cases, so we implement something to reach similar goals. We refuse freeing
a partition while their child paritions are not freed - and we request deletion of these
partitions so that they are deleted on next slapos node report run. Then after a few runs,
the whole partition tree will be completely destroyed and freed, even for software which
does not implement deletion properly.
parent 3bcc0c74
...@@ -302,7 +302,40 @@ def stoppedComputerPartition(): ...@@ -302,7 +302,40 @@ def stoppedComputerPartition():
@app.route('/destroyedComputerPartition', methods=['POST']) @app.route('/destroyedComputerPartition', methods=['POST'])
def destroyedComputerPartition(): def destroyedComputerPartition():
return 'Ignored' if not (request.form['computer_partition_id'] and request.form['computer_id']):
raise ValueError("computer_partition_id and computer_id are required")
# Implement something similar to Alarm_garbageCollectDestroyUnlinkedInstance, if root instance
# is destroyed, we request child instances in deleted state
execute_db(
'partition',
'UPDATE %s SET requested_state="destroyed" where requested_by=? and computer_reference=?',
[request.form['computer_partition_id'], request.form['computer_id']])
non_destroyed_child_partitions = [
p['reference'] for p in execute_db(
'partition',
'SELECT reference FROM %s WHERE requested_by=? AND computer_reference=?',
[request.form['computer_partition_id'], request.form['computer_id']])]
if non_destroyed_child_partitions:
return "Not destroying yet because this partition has child partitions: %s" % (
', '.join(non_destroyed_child_partitions))
execute_db(
'partition',
'UPDATE %s SET '
' slap_state="free",'
' software_release=NULL,'
' xml=NULL,'
' connection_xml=NULL,'
' slave_instance_list=NULL,'
' software_type=NULL,'
' partition_reference=NULL,'
' requested_by=NULL,'
' requested_state="started"'
'WHERE reference=? AND computer_reference=? ',
[request.form['computer_partition_id'], request.form['computer_id']])
return 'OK'
@app.route('/useComputer', methods=['POST']) @app.route('/useComputer', methods=['POST'])
def useComputer(): def useComputer():
......
...@@ -836,6 +836,111 @@ class TestRequest(MasterMixin): ...@@ -836,6 +836,111 @@ class TestRequest(MasterMixin):
'/', '/',
request.getConnectionParameterDict()['path']) request.getConnectionParameterDict()['path'])
def test_destroy_partition(self):
self.format_for_number_of_partitions(1)
partition = self.request('http://sr//', None, 'MyFirstInstance')
self.assertEqual(partition.getState(), 'started')
partition = self.request('http://sr//', None, 'MyFirstInstance', state='destroyed')
self.assertEqual(partition.getState(), 'destroyed')
partition_data, = slapos.proxy.views.execute_db(
'partition',
'SELECT * from %s where reference=?',
(partition.getId(),),
db=sqlite_connect(self.proxy_db))
self.assertEqual(partition_data['requested_state'], 'destroyed')
self.assertEqual(partition_data['slap_state'], 'busy')
self.assertEqual(partition_data['software_release'], 'http://sr//')
self.assertEqual(partition_data['partition_reference'], 'MyFirstInstance')
partition.destroyed() # this is what `slapos node report` call
# now partition is free
partition_data, = slapos.proxy.views.execute_db(
'partition',
'SELECT * from %s where reference=?',
(partition.getId(),),
db=sqlite_connect(self.proxy_db))
self.assertEqual(partition_data['requested_state'], 'started')
self.assertEqual(partition_data['slap_state'], 'free')
self.assertIsNone(partition_data['partition_reference'])
# and we can request new partitions
self.request('http://sr//', None, 'Another instance')
def test_destroy_child_partition(self):
self.format_for_number_of_partitions(2)
partition_parent = self.request('http://sr//', None, 'MyFirstInstance')
partition_child = self.request('http://sr//', None, 'MySubInstance', partition_parent.getId())
self.assertEqual(partition_parent.getState(), 'started')
self.assertEqual(partition_child.getState(), 'started')
partition_parent = self.request('http://sr//', None, 'MyFirstInstance', state='destroyed')
self.assertEqual(
slapos.proxy.views.execute_db(
'partition',
'SELECT reference, slap_state, requested_by, requested_state from %s order by requested_by',
db=sqlite_connect(self.proxy_db),
), [
{
'reference': partition_parent.getId(),
'slap_state': 'busy',
'requested_by': None,
'requested_state': 'destroyed',
},
{
'reference': partition_child.getId(),
'slap_state': 'busy',
'requested_by': partition_parent.getId(),
'requested_state': 'started',
},
])
# destroying the parent partition will first mark the dependent partitions as destroyed
partition_parent.destroyed()
self.assertEqual(
slapos.proxy.views.execute_db(
'partition',
'SELECT reference, slap_state, requested_by, requested_state from %s order by requested_by',
db=sqlite_connect(self.proxy_db),
), [
{
'reference': partition_parent.getId(),
'slap_state': 'busy',
'requested_by': None,
'requested_state': 'destroyed',
},
{
'reference': partition_child.getId(),
'slap_state': 'busy',
'requested_by': partition_parent.getId(),
'requested_state': 'destroyed',
},
])
partition_parent.destroyed()
partition_child.destroyed()
partition_parent.destroyed()
self.assertEqual(
slapos.proxy.views.execute_db(
'partition',
'SELECT reference, slap_state, requested_by, requested_state from %s order by requested_by',
db=sqlite_connect(self.proxy_db),
), [
{
'reference': partition_parent.getId(),
'slap_state': 'free',
'requested_by': None,
'requested_state': 'started',
},
{
'reference': partition_child.getId(),
'slap_state': 'free',
'requested_by': None,
'requested_state': 'started',
},
])
class TestSlaveRequest(MasterMixin): class TestSlaveRequest(MasterMixin):
""" """
......
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