Commit c8fd80f8 authored by Romain Courteaud's avatar Romain Courteaud

slapos_cloud: reduce the number of alarm calls

parent 8cf975dc
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject()
alarm_tool = portal.portal_alarms
# Higher than simulable movement priority
PRIORITY = 4
# priority=3, to be executed after all reindex, but also execute simulation _expand
PRIORITY = 3
if alarm_tool.isSubscribed() and len(alarm_id_list):
# No alarm tool is not subscribed, respect this choice and do not activate any alarm
......@@ -14,6 +19,8 @@ if alarm_tool.isSubscribed() and len(alarm_id_list):
for alarm_id in alarm_id_list:
alarm = alarm_tool.restrictedTraverse(alarm_id)
deduplication_tag = 'Base_reindexAndSenseAlarm_%s' % alarm_id
if alarm.isEnabled():
# do nothing if the alarm is not enabled
......@@ -21,24 +28,28 @@ if alarm_tool.isSubscribed() and len(alarm_id_list):
activate_kw = {}
activate_kw['activity'] = 'SQLQueue'
activate_kw['after_tag'] = tag
# Wait for the previous alarm run to be finished
context.activate(**activate_kw).Base_reindexAndSenseAlarm([alarm_id],
must_reindex_context=False)
elif alarm.isActive():
activate_kw = {}
activate_kw['priority'] = PRIORITY
activate_kw['after_path_and_method_id'] = (alarm.getPath(), 'getId')
# Wait for the previous alarm run to be finished
# call on alarm tool to gather and drop with sqldict
activate_kw['tag'] = deduplication_tag
activate_kw['priority'] = max(1, PRIORITY-1)
# Wait for the context indexation to be finished
alarm_tool.activate(**activate_kw).Base_reindexAndSenseAlarm([alarm_id],
must_reindex_context=False)
else:
# Do not call directly call activeSense, because in case of concurrency,
# it will trigger the alarm multiple time in parallel
# Wait for the previous alarm run to be finished
# wait for the context to be reindexed before activating the alarm
# ROMAIN: getId is used, because most alarm script ends with an getId activity
# priority=3, to be executed after all reindex, but also execute simulation _expand
alarm.activate(priority=PRIORITY).activeSense()
# Prevent 2 nodes to call activateSense concurrently
alarm.serialize()
elif portal.portal_activities.countMessageWithTag(deduplication_tag) <= 1:
if alarm.isActive():
# If the alarm is active, wait for it
# and try to reduce the number of activities
# to reduce the number of alarm execution
activate_kw = {}
activate_kw['activity'] = 'SQLQueue'
activate_kw['priority'] = PRIORITY
activate_kw['tag'] = deduplication_tag
activate_kw['after_path'] = alarm.getPath()
# Wait for the previous alarm run to be finished
# call on alarm tool to gather and drop with sqldict
alarm_tool.activate(**activate_kw).Base_reindexAndSenseAlarm([alarm_id],
must_reindex_context=False)
else:
# activeSense create an activity in SQLDict
alarm.activeSense()
# Prevent 2 nodes to call activateSense concurrently
alarm.serialize()
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>alarm_id_list, must_reindex_context=True</string> </value>
<value> <string>alarm_id_list, must_reindex_context=True, REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013-2019 Nexedi SA and Contributors.
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
#
##############################################################################
import transaction
from zExceptions import Unauthorized
from DateTime import DateTime
from erp5.component.test.SlapOSTestCaseMixin import \
SlapOSTestCaseMixinWithAbort, TemporaryAlarmScript
class TestBase_reindexAndSenseAlarm(SlapOSTestCaseMixinWithAbort):
def afterSetUp(self):
# Ensure the alarms has a workflow history
for alarm_id in ['slapos_allocate_instance',
'slapos_free_compute_partition']:
alarm = self.portal.portal_alarms[alarm_id]
old_comment = alarm.getProperty('comment')
alarm.edit(comment='%s foo' % old_comment)
alarm.edit(comment=old_comment)
return super(TestBase_reindexAndSenseAlarm, self).afterSetUp()
def getIndexationDate(self, document):
return DateTime(self.portal.portal_catalog(
uid=document.getUid(),
select_list=['indexation_timestamp']
)[0].indexation_timestamp)
def test_reindexAndSenseAlarm_REQUEST_disallowed(self):
document = self.portal.internal_order_module
self.assertRaises(
Unauthorized,
document.Base_reindexAndSenseAlarm,
[],
REQUEST={})
def test_reindexAndSenseAlarm_callAlarmAfterContextReindex(self):
# Check that the alarm is triggered
# only after the context is reindexed
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
previous_indexation_timestamp = self.getIndexationDate(document)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp = self.getIndexationDate(document)
edit_timestamp = alarm.getModificationDate()
# check that the document has been reindexed
self.assertTrue(previous_indexation_timestamp < next_indexation_timestamp)
# check that alarm was called after the object was reindexed
self.assertTrue(next_indexation_timestamp < edit_timestamp)
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_callAlarmWithoutContextReindex(self):
# Check that the alarm is triggered
# without reindexing the context
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'],
must_reindex_context=False)
previous_indexation_timestamp = self.getIndexationDate(document)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp = self.getIndexationDate(document)
edit_timestamp = alarm.getModificationDate()
# check that the document was not reindexed
self.assertEquals(previous_indexation_timestamp, next_indexation_timestamp)
# check that alarm was called after the object was reindexed
self.assertTrue(next_indexation_timestamp < edit_timestamp)
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_doesNotReindexIfNoAlarm(self):
# Check that no alarm is triggered
# and the context is not reindexed
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document.Base_reindexAndSenseAlarm([])
previous_indexation_timestamp = self.getIndexationDate(document)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp = self.getIndexationDate(document)
# check that the document was not reindex
self.assertEquals(previous_indexation_timestamp, next_indexation_timestamp)
# check that the alarm was not triggered
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count
)
def test_reindexAndSenseAlarm_twiceInTheSameTransaction(self):
# Check that the alarm is triggered only ONCE
# if the script is called twice in a transaction
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
previous_indexation_timestamp = self.getIndexationDate(document)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp = self.getIndexationDate(document)
edit_timestamp = alarm.getModificationDate()
# check that the document has been reindexed
self.assertTrue(previous_indexation_timestamp < next_indexation_timestamp)
# check that alarm was called ONCE after the object was reindexed
self.assertTrue(next_indexation_timestamp < edit_timestamp)
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_twiceInTheSameTransactionWithoutReindex(self):
# Check that the alarm is triggered only ONCE
# if the script is called twice in a transaction
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'],
must_reindex_context=False)
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'],
must_reindex_context=False)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
# check that alarm was called ONCE
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_twiceInTheTwoTransactions(self):
# Check that the alarm is triggered only ONCE
# if the script is called twice in a transaction
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
transaction.commit()
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
previous_indexation_timestamp = self.getIndexationDate(document)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp = self.getIndexationDate(document)
edit_timestamp = alarm.getModificationDate()
# check that the document has been reindexed
self.assertTrue(previous_indexation_timestamp < next_indexation_timestamp)
# check that alarm was called ONCE after the object was reindexed
self.assertTrue(next_indexation_timestamp < edit_timestamp)
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_alarmActive(self):
# Check that the script wait for the alarm to be not activate
# before triggering it again
document = self.portal.internal_order_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
tag = 'foobar'
alarm.activate(tag=tag).getId()
# Call edit, to ensure the last edit contains the comment value
alarm.activate(after_tag=tag, tag=tag+'1').edit(description=alarm.getDescription() + ' ')
alarm.activate(after_tag=tag+'1').edit(description=alarm.getDescription()[:-1])
transaction.commit()
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance'],
must_reindex_context=False)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 3
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_twoContextSameTransaction(self):
# Check that the script wait for the alarm to be not activate
# before triggering it again
document1 = self.portal.internal_order_module
document2 = self.portal.internal_packing_list_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document1.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
document2.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
previous_indexation_timestamp1 = self.getIndexationDate(document1)
previous_indexation_timestamp2 = self.getIndexationDate(document2)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp1 = self.getIndexationDate(document1)
next_indexation_timestamp2 = self.getIndexationDate(document2)
edit_timestamp = alarm.getModificationDate()
# check that the document has been reindexed
self.assertTrue(previous_indexation_timestamp1 < next_indexation_timestamp1)
self.assertTrue(previous_indexation_timestamp2 < next_indexation_timestamp2)
# check that alarm was called after the object was reindexed
self.assertTrue(next_indexation_timestamp1 < edit_timestamp)
self.assertTrue(next_indexation_timestamp2 < edit_timestamp)
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_twoContextDifferentTransaction(self):
# Check that the script wait for the alarm to be not activate
# before triggering it again
document1 = self.portal.internal_order_module
document2 = self.portal.internal_packing_list_module
alarm = self.portal.portal_alarms.slapos_allocate_instance
document1.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
document2.Base_reindexAndSenseAlarm(['slapos_allocate_instance'])
previous_indexation_timestamp1 = self.getIndexationDate(document1)
transaction.commit()
previous_indexation_timestamp2 = self.getIndexationDate(document2)
workflow_history_count = len(alarm.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm, 'Alarm_allocateInstance'):
self.tic()
next_indexation_timestamp1 = self.getIndexationDate(document1)
next_indexation_timestamp2 = self.getIndexationDate(document2)
edit_timestamp = alarm.getModificationDate()
# check that the document has been reindexed
self.assertTrue(previous_indexation_timestamp1 < next_indexation_timestamp1)
self.assertTrue(previous_indexation_timestamp2 < next_indexation_timestamp2)
# check that alarm was called after the object was reindexed
self.assertTrue(next_indexation_timestamp1 < edit_timestamp)
self.assertTrue(next_indexation_timestamp2 < edit_timestamp)
self.assertEquals(
len(alarm.workflow_history['edit_workflow']),
workflow_history_count + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm.workflow_history['edit_workflow'][-1]['comment']
)
def test_reindexAndSenseAlarm_twoAlarm(self):
# Check that the script wait for the alarm to be not activate
# before triggering it again
document = self.portal.internal_order_module
alarm1 = self.portal.portal_alarms.slapos_allocate_instance
alarm2 = self.portal.portal_alarms.slapos_free_compute_partition
document.Base_reindexAndSenseAlarm(['slapos_allocate_instance',
'slapos_free_compute_partition'])
previous_indexation_timestamp = self.getIndexationDate(document)
workflow_history_count1 = len(alarm1.workflow_history['edit_workflow'])
workflow_history_count2 = len(alarm2.workflow_history['edit_workflow'])
with TemporaryAlarmScript(alarm1, 'Alarm_allocateInstance'):
with TemporaryAlarmScript(alarm2, 'Alarm_searchComputePartitionAndMarkFree'):
self.tic()
next_indexation_timestamp = self.getIndexationDate(document)
edit_timestamp1 = alarm1.getModificationDate()
edit_timestamp2 = alarm2.getModificationDate()
self.assertTrue(previous_indexation_timestamp < next_indexation_timestamp)
# check that alarm was called after the object was reindexed
self.assertTrue(next_indexation_timestamp < edit_timestamp1)
self.assertTrue(next_indexation_timestamp < edit_timestamp2)
self.assertEquals(
len(alarm1.workflow_history['edit_workflow']),
workflow_history_count1 + 1
)
self.assertEqual(
'Visited by Alarm_allocateInstance',
alarm1.workflow_history['edit_workflow'][-1]['comment']
)
self.assertEquals(
len(alarm2.workflow_history['edit_workflow']),
workflow_history_count2 + 1
)
self.assertEqual(
'Visited by Alarm_searchComputePartitionAndMarkFree',
alarm2.workflow_history['edit_workflow'][-1]['comment']
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testSlapOSCloudSkins</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSCloudSkins</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
test.erp5.testSlapOSCloudAlarm
test.erp5.SlapOSTestCaseDefaultScenarioMixin
test.erp5.SlapOSTestCaseMixin
test.erp5.testSlapOSAllocationSupply
test.erp5.testSlapOSCloud
test.erp5.testSlapOSCloudAlarm
test.erp5.testSlapOSCloudAllocationAlarm
test.erp5.testSlapOSCloudComputeNodeSlapInterfaceWorkflow
test.erp5.testSlapOSCloudComputePartitionSlapInterfaceWorkflow
test.erp5.testSlapOSCloudConstraint
test.erp5.testSlapOSCloudInstanceSlapInterfaceWorkflow
test.erp5.testSlapOSCloudInteractionWorkflow
test.erp5.testSlapOSCloudPersonSlapInterfaceWorkflow
test.erp5.testSlapOSCloudNetworkSlapInterfaceWorkflow
test.erp5.testSlapOSCloudComputeNodeSlapInterfaceWorkflow
test.erp5.testSlapOSCloudInstanceSlapInterfaceWorkflow
test.erp5.testSlapOSCloudPersonSlapInterfaceWorkflow
test.erp5.testSlapOSCloudProjectSlapInterfaceWorkflow
test.erp5.testSlapOSCloudSecurityGroup
test.erp5.testSlapOSCloudConstraint
test.erp5.testSlapOSCloudUpgrader
test.erp5.testSlapOSCloudShadow
test.erp5.SlapOSTestCaseMixin
test.erp5.SlapOSTestCaseDefaultScenarioMixin
test.erp5.testSlapOSCloud
\ No newline at end of file
test.erp5.testSlapOSCloudSkins
test.erp5.testSlapOSCloudUpgrader
\ No newline at end of file
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