Commit 20a0f457 authored by Rafael Monnerat's avatar Rafael Monnerat

Disable Disconnection of instance on the graph

See merge request !591
parents c988e4a8 2ebb32ba
Pipeline #32011 failed with stage
in 0 seconds
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Alarm" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_garbageCollectDestroyUnlinkedInstance</string> </value>
</item>
<item>
<key> <string>automatic_solve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_garbage_collect_destroy_unlinked_instance</string> </value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_hour_frequency</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_minute_frequency</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>periodicity_month</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_start_date</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>15638400.0</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>periodicity_week</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Alarm</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Garbage Collect Unlinked Instances</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
portal.portal_catalog.searchAndActivate(
portal_type=["Software Instance", "Slave Instance"],
validation_state="validated",
specialise_validation_state="validated",
successor_related_uid=SimpleQuery(successor_related_uid=None, comparison_operator='is'),
method_id='SoftwareInstance_tryToGarbageUnlinkedInstance',
activate_kw={'tag': tag}
)
context.activate(after_tag=tag).getId()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>tag, fixit, params</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_garbageCollectDestroyUnlinkedInstance</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from zExceptions import Unauthorized
from DateTime import DateTime
from erp5.component.module.DateUtils import addToDate
if REQUEST is not None:
raise Unauthorized
instance = context
def checkInstanceTree(instance_list):
"""
Check if successor link is really removed to this instance
"""
sub_instance_list = []
if instance_list == []:
return
for item in instance_list:
if item.getUid() == instance.getUid():
return item
sub_instance_list.extend(item.getSuccessorValueList())
return checkInstanceTree(sub_instance_list)
if instance.getSlapState() == "destroy_requested":
return
instance_tree = instance.getSpecialiseValue()
if instance_tree is None or \
instance_tree.getSlapState() == "destroy_requested":
return
root_instance = instance_tree.getSuccessorValue()
if root_instance is None:
# Refuse to destroy root instance
raise ValueError("Instance Tree %s has no root instance, this should "\
"not happen!!" % instance_tree.getRelativeUrl())
# If instance modificationDate is too recent, skip
# Delay destroy of unlinked instances
if instance.getModificationDate() - addToDate(DateTime(), {'minute': -1*delay_time}) > 0:
return
if checkInstanceTree([root_instance]) is None:
# This unlinked instance to parent should be removed
is_slave = False
if instance.getPortalType() == 'Slave Instance':
is_slave = True
promise_kw = {
'instance_xml': instance.getTextContent(),
'software_type': instance.getSourceReference(),
'sla_xml': instance.getSlaXml(),
'software_release': instance.getUrlString(),
'shared': is_slave,
}
instance.requestDestroy(**promise_kw)
# Unlink all children of this instance
instance.edit(successor="", comment="Destroyed garbage collector!")
return instance.getRelativeUrl()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>REQUEST=None, delay_time=50</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SoftwareInstance_tryToGarbageUnlinkedInstance</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved. # Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
import transaction import transaction
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin
import time
from zExceptions import Unauthorized from zExceptions import Unauthorized
from DateTime import DateTime from DateTime import DateTime
from erp5.component.module.DateUtils import addToDate from erp5.component.module.DateUtils import addToDate
...@@ -848,227 +847,6 @@ class TestSlapOSGarbageCollectNonAllocatedRootTreeAlarm(SlapOSTestCaseMixin): ...@@ -848,227 +847,6 @@ class TestSlapOSGarbageCollectNonAllocatedRootTreeAlarm(SlapOSTestCaseMixin):
) )
class TestSlapOSGarbageCollectUnlinkedInstanceAlarm(SlapOSTestCaseMixin):
def createInstance(self):
instance_tree = self.portal.instance_tree_module\
.template_instance_tree.Base_createCloneDocument(batch_mode=1)
instance_tree.validate()
instance_tree.edit(
title=self.generateNewSoftwareTitle(),
reference="TESTHS-%s" % self.generateNewId(),
)
request_kw = dict(
software_release=\
self.generateNewSoftwareReleaseUrl(),
software_type=self.generateNewSoftwareType(),
instance_xml=self.generateSafeXml(),
sla_xml=self.generateSafeXml(),
shared=False,
software_title=instance_tree.getTitle(),
state='started'
)
instance_tree.requestStart(**request_kw)
instance_tree.requestInstance(**request_kw)
self.instance_tree = instance_tree
instance = instance_tree.getSuccessorValue()
return instance
def createComputePartition(self):
compute_node = self.portal.compute_node_module\
.template_compute_node.Base_createCloneDocument(batch_mode=1)
compute_node.validate()
compute_node.edit(
title=self.generateNewSoftwareTitle(),
reference="TESTCOMP-%s" % self.generateNewId(),
)
partition = compute_node.newContent(portal_type="Compute Partition")
return partition
def doRequestInstance(self, instance, title, slave=False):
instance_kw = dict(software_release=self.generateNewSoftwareReleaseUrl(),
software_type=self.generateNewSoftwareType(),
instance_xml=self.generateSafeXml(),
sla_xml=self.generateSafeXml(),
shared=slave,
software_title=title,
state='started'
)
instance.requestInstance(**instance_kw)
self.tic()
sub_instance = instance.getSuccessorValue()
partition = self.createComputePartition()
sub_instance.edit(aggregate_value=partition)
self.tic()
self.assertEqual(self.instance_tree.getRelativeUrl(),
sub_instance.getSpecialise())
return sub_instance
def test_SoftwareInstance_tryToGarbageUnlinkedInstance(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
instance0 = self.doRequestInstance(instance, 'instance0')
self.assertEqual(instance0.getSuccessorRelatedTitle(), instance.getTitle())
# Remove successor link
instance.edit(successor_list=[])
self.tic()
self.assertEqual(instance0.getSuccessorRelatedTitle(), None)
instance0.SoftwareInstance_tryToGarbageUnlinkedInstance(delay_time=-1)
self.tic()
self.assertEqual(instance0.getSlapState(), 'destroy_requested')
def test_SoftwareInstance_tryToGarbageUnlinkedInstance_hosting_destroyed(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
instance0 = self.doRequestInstance(instance, 'instance0')
instance.edit(successor_list=[])
self.tic()
self.instance_tree.archive()
self.portal.portal_workflow._jumpToStateFor(self.instance_tree,
'destroy_requested')
self.portal.portal_workflow._jumpToStateFor(instance, 'destroy_requested')
self.tic()
instance0.SoftwareInstance_tryToGarbageUnlinkedInstance()
self.tic()
# Will not be destroyed by this script
self.assertEqual(instance0.getSlapState(), 'start_requested')
def test_SoftwareInstance_tryToGarbageUnlinkedInstance_will_unlink_children(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
instance0 = self.doRequestInstance(instance, 'instance0')
instance_instance0 = self.doRequestInstance(instance0, 'Subinstance0')
self.assertEqual(instance_instance0.getSuccessorRelatedTitle(),
'instance0')
instance.edit(successor_list=[])
self.tic()
self.assertEqual(instance0.getSuccessorRelatedTitle(), None)
instance0.SoftwareInstance_tryToGarbageUnlinkedInstance(delay_time=-1)
self.tic()
self.assertEqual(instance0.getSlapState(), 'destroy_requested')
self.assertEqual(instance_instance0.getSlapState(), 'start_requested')
# Link of child removed
self.assertEqual(instance_instance0.getSuccessorRelatedTitle(), None)
def test_SoftwareInstance_tryToGarbageUnlinkedInstance_will_delay(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
instance0 = self.doRequestInstance(instance, 'instance0')
instance_instance0 = self.doRequestInstance(instance0, 'Subinstance0')
self.assertEqual(instance_instance0.getSuccessorRelatedTitle(),
'instance0')
instance.edit(successor_list=[])
self.tic()
self.assertEqual(instance0.getSuccessorRelatedTitle(), None)
instance0.SoftwareInstance_tryToGarbageUnlinkedInstance()
self.tic()
self.assertEqual(instance0.getSlapState(), 'start_requested')
self.assertEqual(instance_instance0.getSlapState(), 'start_requested')
# delay a bit
time.sleep(2)
# run with delay of 3 seconds
instance0.SoftwareInstance_tryToGarbageUnlinkedInstance(delay_time=3/60.0)
self.tic()
self.assertEqual(instance0.getSlapState(), 'destroy_requested')
self.assertEqual(instance_instance0.getSlapState(), 'start_requested')
# Link of child removed
self.assertEqual(instance_instance0.getSuccessorRelatedTitle(), None)
def test_SoftwareInstance_tryToGarbageUnlinkedInstance_unlinked_root(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
self.assertEqual(self.instance_tree.getTitle(), instance.getTitle())
# Remove successor link
self.instance_tree.edit(successor_list=[])
self.tic()
self.assertEqual(instance.getSuccessorRelatedTitle(), None)
# will not destroy
self.assertRaises(
ValueError,
instance.SoftwareInstance_tryToGarbageUnlinkedInstance,
delay_time=-10)
self.tic()
self.assertEqual(instance.getSlapState(), 'start_requested')
def test_SoftwareInstance_tryToGarbageUnlinkedInstance_not_unlinked(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
instance0 = self.doRequestInstance(instance, 'instance0')
instance_instance0 = self.doRequestInstance(instance0, 'Subinstance0')
self.assertEqual(instance_instance0.getSuccessorRelatedTitle(),
'instance0')
self.assertEqual(instance_instance0.getSlapState(), 'start_requested')
# Try to remove without delete successor link
instance_instance0.SoftwareInstance_tryToGarbageUnlinkedInstance(delay_time=-1)
self.tic()
self.assertEqual(instance_instance0.getSlapState(), 'start_requested')
def test_alarm_search_inlinked_instance(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
instance0 = self.doRequestInstance(instance, 'instance0')
self.assertEqual(instance.getSuccessorReference(),
instance0.getReference())
self._test_alarm_not_visited(
self.portal.portal_alarms.slapos_garbage_collect_destroy_unlinked_instance,
instance0,
'SoftwareInstance_tryToGarbageUnlinkedInstance'
)
# Remove successor link
instance.edit(successor_list=[])
self._test_alarm(
self.portal.portal_alarms.slapos_garbage_collect_destroy_unlinked_instance,
instance0,
'SoftwareInstance_tryToGarbageUnlinkedInstance'
)
def test_alarm_search_inlinked_instance_slave(self):
instance = self.createInstance()
partition = self.createComputePartition()
instance.edit(aggregate_value=partition)
self.tic()
slave_instance0 = self.doRequestInstance(instance, 'slaveInstance0', True)
self.assertEqual(instance.getSuccessorTitle(), 'slaveInstance0')
self._test_alarm_not_visited(
self.portal.portal_alarms.slapos_garbage_collect_destroy_unlinked_instance,
slave_instance0,
'SoftwareInstance_tryToGarbageUnlinkedInstance'
)
instance.edit(successor_list=[])
self._test_alarm(
self.portal.portal_alarms.slapos_garbage_collect_destroy_unlinked_instance,
slave_instance0,
'SoftwareInstance_tryToGarbageUnlinkedInstance'
)
class TestSlapOSInvalidateDestroyedInstance(SlapOSTestCaseMixin): class TestSlapOSInvalidateDestroyedInstance(SlapOSTestCaseMixin):
def createSoftwareInstance(self): def createSoftwareInstance(self):
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
# #
############################################################################## ##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin
from erp5.component.document.SoftwareInstance import SoftwareInstance, \
DisconnectedSoftwareTree, CyclicSoftwareTree
import transaction import transaction
from time import sleep from time import sleep
from zExceptions import Unauthorized from zExceptions import Unauthorized
...@@ -408,6 +410,127 @@ class TestSlapOSCoreInstanceSlapInterfaceWorkflow(SlapOSTestCaseMixin): ...@@ -408,6 +410,127 @@ class TestSlapOSCoreInstanceSlapInterfaceWorkflow(SlapOSTestCaseMixin):
self.assertSameSet(B_instance.getSuccessorList(), self.assertSameSet(B_instance.getSuccessorList(),
[C_instance.getRelativeUrl()]) [C_instance.getRelativeUrl()])
def test_request_tree_disconnected(self):
"""Checks tree change forced by request
For a tree like:
A
|
A
|\
B C
Them force C to be disconnected:
A
|
A
|
B C
When A requests C the request should fail.
"""
request_kw = self.request_kw.copy()
request_kw['software_title'] = self.generateNewSoftwareTitle()
self.software_instance.requestInstance(**request_kw)
B_instance = self.software_instance.REQUEST.get('request_instance')
request_kw['software_title'] = self.generateNewSoftwareTitle()
self.software_instance.requestInstance(**request_kw)
C_instance = self.software_instance.REQUEST.get('request_instance')
self.assertSameSet(
self.software_instance.getSuccessorList(),
[B_instance.getRelativeUrl(), C_instance.getRelativeUrl()])
self.tic() # in order to recalculate tree
self.software_instance.setSuccessorList([B_instance.getRelativeUrl()])
self.tic() # in order to recalculate tree
self.assertRaises(DisconnectedSoftwareTree, self.software_instance.requestInstance, **request_kw)
# Just re-set sucessor fixes the problem
self.software_instance.setSuccessorList([B_instance.getRelativeUrl(), C_instance.getRelativeUrl()])
self.tic() # in order to recalculate tree
self.software_instance.requestInstance(**request_kw)
C1_instance = self.software_instance.REQUEST.get('request_instance')
self.assertEqual(C_instance.getRelativeUrl(), C1_instance.getRelativeUrl())
def test_request_check_cycle(self):
"""Checks tree change forced by request
For a tree like:
A
|
A
|
B
|
C
|
D
Them force D to request A to be cycled (dupplicated successor related):
A
|
A
|\
B D
|/
C
In normal conditions there is no raise because either DisconnectedSoftwareTree is raised before,
so we hot patch the checkConnected to ensure we get the proper raise
"""
request_kw = self.request_kw.copy()
request_kw['software_title'] = self.generateNewSoftwareTitle()
self.software_instance.requestInstance(**request_kw)
B_instance = self.software_instance.REQUEST.get('request_instance')
request_kw['software_title'] = self.generateNewSoftwareTitle()
B_instance.requestInstance(**request_kw)
C_instance = self.software_instance.REQUEST.get('request_instance')
request_kw['software_title'] = self.generateNewSoftwareTitle()
C_instance.requestInstance(**request_kw)
D_instance = self.software_instance.REQUEST.get('request_instance')
self.assertSameSet(
self.software_instance.getSuccessorList(),
[B_instance.getRelativeUrl()])
self.assertSameSet(
B_instance.getSuccessorList(), [C_instance.getRelativeUrl()])
self.assertSameSet(
C_instance.getSuccessorList(), [D_instance.getRelativeUrl()])
self.tic() # in order to recalculate tree
def checkConnected(self, graph, root):
"Patch and return skip"
return
checkConnected_original = SoftwareInstance.checkConnected
try:
SoftwareInstance.checkConnected = checkConnected
request_kw['software_title'] = B_instance.getTitle()
self.assertRaises(CyclicSoftwareTree, D_instance.requestInstance, **request_kw)
finally:
SoftwareInstance.checkConnected = checkConnected_original
def test_request_tree_change_not_indexed(self): def test_request_tree_change_not_indexed(self):
"""Checks tree change forced by request """Checks tree change forced by request
......
...@@ -12,7 +12,6 @@ portal_alarms/slapos_allocate_instance ...@@ -12,7 +12,6 @@ portal_alarms/slapos_allocate_instance
portal_alarms/slapos_assert_instance_tree_successor portal_alarms/slapos_assert_instance_tree_successor
portal_alarms/slapos_cloud_invalidate_destroyed_instance portal_alarms/slapos_cloud_invalidate_destroyed_instance
portal_alarms/slapos_free_compute_partition portal_alarms/slapos_free_compute_partition
portal_alarms/slapos_garbage_collect_destroy_unlinked_instance
portal_alarms/slapos_garbage_collect_destroyed_root_tree portal_alarms/slapos_garbage_collect_destroyed_root_tree
portal_alarms/slapos_garbage_collect_non_allocated_root_tree portal_alarms/slapos_garbage_collect_non_allocated_root_tree
portal_alarms/slapos_stop_collect_instance portal_alarms/slapos_stop_collect_instance
......
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