Commit 0448cd02 authored by Jérome Perrin's avatar Jérome Perrin

Support partitions destruction in slap proxy

See merge request nexedi/slapos.core!250
parents 1b56afdc 846a829e
...@@ -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():
......
...@@ -700,7 +700,8 @@ class SlapOSInstanceTestCase(unittest.TestCase): ...@@ -700,7 +700,8 @@ class SlapOSInstanceTestCase(unittest.TestCase):
cls._storeSystemSnapshot( cls._storeSystemSnapshot(
"{}._cleanup request destroy".format(snapshot_name)) "{}._cleanup request destroy".format(snapshot_name))
try: try:
cls.slap.waitForReport(max_retry=cls.report_max_retry, debug=cls._debug) for _ in range(3):
cls.slap.waitForReport(max_retry=cls.report_max_retry, debug=cls._debug)
except: except:
cls.logger.exception("Error during actual destruction") cls.logger.exception("Error during actual destruction")
cls._storeSystemSnapshot( cls._storeSystemSnapshot(
...@@ -731,7 +732,8 @@ class SlapOSInstanceTestCase(unittest.TestCase): ...@@ -731,7 +732,8 @@ class SlapOSInstanceTestCase(unittest.TestCase):
"{}._cleanup leaked_partitions request destruction".format( "{}._cleanup leaked_partitions request destruction".format(
snapshot_name)) snapshot_name))
try: try:
cls.slap.waitForReport(max_retry=cls.report_max_retry, debug=cls._debug) for _ in range(3):
cls.slap.waitForReport(max_retry=cls.report_max_retry, debug=cls._debug)
except: except:
cls.logger.exception( cls.logger.exception(
"Error during leaked partitions actual destruction") "Error during leaked partitions actual destruction")
......
...@@ -440,6 +440,15 @@ class MasterMixin(BasicMixin, unittest.TestCase): ...@@ -440,6 +440,15 @@ class MasterMixin(BasicMixin, unittest.TestCase):
self.assertLessEqual( self.assertLessEqual(
float(computer_partition._parameter_dict['timestamp']), requested_at) float(computer_partition._parameter_dict['timestamp']), requested_at)
app = self.app
class TestConnectionHelper:
def GET(self, path, params=None, headers=None):
return app.get(path, query_string=params, data=data).data
def POST(self, path, params=None, data=None,
content_type='application/x-www-form-urlencoded'):
return app.post(path, query_string=params, data=data).data
computer_partition._connection_helper = TestConnectionHelper()
return computer_partition return computer_partition
def supply(self, url, computer_id=None, state='available'): def supply(self, url, computer_id=None, state='available'):
...@@ -582,7 +591,9 @@ class TestRequest(MasterMixin): ...@@ -582,7 +591,9 @@ class TestRequest(MasterMixin):
self.assertLessEqual(float(str(requested_at)), self.assertLessEqual(float(str(requested_at)),
float(partition._parameter_dict['timestamp'])) float(partition._parameter_dict['timestamp']))
time.sleep(.1) # check timestamp does not change for an identical request time.sleep(.1) # check timestamp does not change for an identical request
self.assertEqual(partition.__dict__, do_request().__dict__) self.assertEqual(
dict(partition.__dict__, _connection_helper=None),
dict(do_request().__dict__, _connection_helper=None))
def test_instance_bang(self): def test_instance_bang(self):
""" """
...@@ -740,8 +751,8 @@ class TestRequest(MasterMixin): ...@@ -740,8 +751,8 @@ class TestRequest(MasterMixin):
""" """
self.format_for_number_of_partitions(2) self.format_for_number_of_partitions(2)
self.assertEqual( self.assertEqual(
self.request('http://sr//', None, 'MyFirstInstance', 'slappart2').__dict__, dict(self.request('http://sr//', None, 'MyFirstInstance', 'slappart2').__dict__, _connection_helper=None),
self.request('http://sr//', None, 'MyFirstInstance', 'slappart3').__dict__) dict(self.request('http://sr//', None, 'MyFirstInstance', 'slappart3').__dict__, _connection_helper=None))
def test_two_different_request_from_one_partition(self): def test_two_different_request_from_one_partition(self):
""" """
...@@ -825,6 +836,111 @@ class TestRequest(MasterMixin): ...@@ -825,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