# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
import transaction
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin, TemporaryAlarmScript
import time
from zExceptions import Unauthorized
from DateTime import DateTime
from erp5.component.module.DateUtils import addToDate


class TestSlapOSCoreSlapOSAssertInstanceTreeSuccessorAlarm(SlapOSTestCaseMixin):
  #################################################################
  # slapos_assert_instance_tree_successor
  #################################################################
  def test_InstanceTree_assertSuccessor_alarm_orphaned(self):
    instance_tree = self.portal.instance_tree_module.newContent(
      portal_type='Instance Tree'
    )
    instance_tree.validate()
    self.tic()
    self._test_alarm(
      self.portal.portal_alarms.slapos_assert_instance_tree_successor,
      instance_tree,
      'InstanceTree_assertSuccessor'
    )

  def test_InstanceTree_assertSuccessor_alarm_renamed(self):
    instance_tree = self.addInstanceTree()
    instance_tree.getSuccessorValue().edit(title=self.generateNewSoftwareTitle())
    self._test_alarm(
      self.portal.portal_alarms.slapos_assert_instance_tree_successor,
      instance_tree,
      'InstanceTree_assertSuccessor'
    )

  def test_InstanceTree_assertSuccessor_alarm_not_renamed(self):
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_assert_instance_tree_successor,
      self.addInstanceTree(),
      'InstanceTree_assertSuccessor'
    )

  #################################################################
  # InstanceTree_assertSuccessor
  #################################################################
  def test_InstanceTree_assertSuccessor_script_renamed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.edit(title=self.generateNewSoftwareTitle())
    self.tic()

    # check that no interaction has recreated the instance
    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertFalse(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

    with TemporaryAlarmScript(self.portal, 'Item_getSubscriptionStatus', "'subscribed'"):
      instance_tree.InstanceTree_assertSuccessor()

    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertTrue(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

  def test_InstanceTree_assertSuccessor_script_renamedAndStopped(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.edit(title=self.generateNewSoftwareTitle())
    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'stop_requested')
    self.tic()

    # check that no interaction has recreated the instance
    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertFalse(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

    with TemporaryAlarmScript(self.portal, 'Item_getSubscriptionStatus', "'subscribed'"):
      instance_tree.InstanceTree_assertSuccessor()

    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertTrue(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

  def test_InstanceTree_assertSuccessor_script_renamedAndDestroyed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.edit(title=self.generateNewSoftwareTitle())
    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'destroy_requested')
    self.tic()

    # check that no interaction has recreated the instance
    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertFalse(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

    with TemporaryAlarmScript(self.portal, 'Item_getSubscriptionStatus', "'subscribed'"):
      instance_tree.InstanceTree_assertSuccessor()

    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertFalse(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

  def test_InstanceTree_assertSuccessor_script_archived(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.edit(title=self.generateNewSoftwareTitle())
    instance_tree.archive()
    self.tic()

    # check that no interaction has recreated the instance
    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertFalse(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())

    with TemporaryAlarmScript(self.portal, 'Item_getSubscriptionStatus', "'subscribed'"):
      instance_tree.InstanceTree_assertSuccessor()

    self.assertNotEqual(instance_tree.getTitle(), software_instance.getTitle())
    self.assertFalse(instance_tree.getTitle() in
        instance_tree.getSuccessorTitleList())


class TestSlapOSFreeComputePartitionAlarm(SlapOSTestCaseMixin):

  #################################################################
  # slapos_free_compute_partition
  #################################################################
  def test_SoftwareInstance_tryToUnallocatePartition_alarm_allocated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')
    # invalidate transition triggers the alarm
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'invalidated')

    self.tic()
    self._test_alarm(
      self.portal.portal_alarms.slapos_free_compute_partition,
      software_instance,
      'SoftwareInstance_tryToUnallocatePartition'
    )

  def test_SoftwareInstance_tryToUnallocatePartition_alarm_sharedAndAllocated(self):
    instance_tree = self.addInstanceTree(shared=True)
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')
    # invalidate transition triggers the alarm
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'invalidated')

    self.tic()
    self._test_alarm(
      self.portal.portal_alarms.slapos_free_compute_partition,
      software_instance,
      'SoftwareInstance_tryToUnallocatePartition'
    )

  def test_SoftwareInstance_tryToUnallocatePartition_alarm_unallocated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')
    # invalidate transition triggers the alarm
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'invalidated')

    self.tic()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_free_compute_partition,
      software_instance,
      'SoftwareInstance_tryToUnallocatePartition'
    )

  def test_SoftwareInstance_tryToUnallocatePartition_alarm_allocatedAndValidated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')

    self.tic()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_free_compute_partition,
      software_instance,
      'SoftwareInstance_tryToUnallocatePartition'
    )

  def test_SoftwareInstance_tryToUnallocatePartition_alarm_allocatedAndStarted(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self.tic()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_free_compute_partition,
      software_instance,
      'SoftwareInstance_tryToUnallocatePartition'
    )

  #################################################################
  # SoftwareInstance_tryToUnallocatePartition
  #################################################################
  def test_SoftwareInstance_tryToUnallocatePartition_script_allocated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')
    self.tic()

    software_instance.SoftwareInstance_tryToUnallocatePartition()
    self.assertEqual(None, software_instance.getAggregate())
    self.assertEqual('free', partition.getSlapState())

  def test_SoftwareInstance_tryToUnallocatePartition_script_sharedAndAllocated(self):
    instance_tree = self.addInstanceTree(shared=True)
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')
    self.tic()

    software_instance.SoftwareInstance_tryToUnallocatePartition()
    self.assertEqual(None, software_instance.getAggregate())
    self.assertEqual('free', partition.getSlapState())

  def test_SoftwareInstance_tryToUnallocatePartition_script_concurrency(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')
    self.tic()

    partition.activate(tag="allocate_%s" % partition.getRelativeUrl()\
        ).getId()
    transaction.commit()
    software_instance.SoftwareInstance_tryToUnallocatePartition()
    self.assertEqual(partition.getRelativeUrl(),
                    software_instance.getAggregate())
    self.assertEqual('busy', partition.getSlapState())

  def test_SoftwareInstance_tryToUnallocatePartition_script_twoInstances(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())
    instance_tree2 = self.addInstanceTree(project=instance_tree.getFollowUpValue())
    software_instance2 = instance_tree2.getSuccessorValue()

    self.tic()

    try:
      # Prevent calling interaction workflows
      software_instance.setCategoryList(
        software_instance.getCategoryList() + ['aggregate/%s' % partition.getRelativeUrl()]
        )
      software_instance2.setCategoryList(
        software_instance2.getCategoryList() + ['aggregate/%s' % partition.getRelativeUrl()]
      )
      self.portal.portal_workflow._jumpToStateFor(partition,
          'busy')
      self.portal.portal_workflow._jumpToStateFor(software_instance,
          'destroy_requested')
      self.tic()

      software_instance.SoftwareInstance_tryToUnallocatePartition()
      self.assertEqual(None, software_instance.getAggregate())
      self.assertEqual('busy', partition.getSlapState())
      self.assertEqual(partition.getRelativeUrl(), software_instance2.getAggregate())
    except:
      # 2 instances linked to a partition crashes _fillComputeNodeInformationCache
      # activity. Clean up to not impact the other tests
      software_instance2.setAggregate(None)
      raise
    finally:
      software_instance2.setAggregate(None)
    self.tic()

  def test_SoftwareInstance_tryToUnallocatePartition_script_notDestroyed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())

    software_instance.setAggregateValue(partition)
    partition.markBusy()
    self.tic()

    software_instance.SoftwareInstance_tryToUnallocatePartition()
    self.assertEqual(partition.getRelativeUrl(), software_instance.getAggregate())
    self.assertEqual('busy', partition.getSlapState())


class TestSlapOSGarbageCollectDestroyedRootTreeAlarm(SlapOSTestCaseMixin):
  #################################################################
  # slapos_garbage_collect_destroyed_root_tree
  #################################################################
  def test_SoftwareInstance_tryToGarbageCollect_alarm_archived(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance_tree.archive()
    self._test_alarm(
      self.portal.portal_alarms.slapos_garbage_collect_destroyed_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollect'
    )

  def test_SoftwareInstance_tryToGarbageCollect_alarm_sharedAndArchived(self):
    instance_tree = self.addInstanceTree(shared=True)
    software_instance = instance_tree.getSuccessorValue()

    instance_tree.archive()
    self._test_alarm(
      self.portal.portal_alarms.slapos_garbage_collect_destroyed_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollect'
    )

  def test_SoftwareInstance_tryToGarbageCollect_alarm_invalidated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance_tree.archive()
    software_instance.invalidate()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_garbage_collect_destroyed_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollect'
    )

  def test_SoftwareInstance_tryToGarbageCollect_alarm_notArchived(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_garbage_collect_destroyed_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollect'
    )

  #################################################################
  # SoftwareInstance_tryToGarbageCollect
  #################################################################
  def test_SoftwareInstance_tryToGarbageCollect_script_destroyed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance_tree.archive()
    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'destroy_requested')
    self.assertEqual('validated',
        software_instance.getValidationState())

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollect()

    self.assertEqual('destroy_requested',
        software_instance.getSlapState())
    self.assertEqual('validated',
        software_instance.getValidationState())

  def test_SoftwareInstance_tryToGarbageCollect_script_archived(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance_tree.archive()

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollect()

    self.assertEqual('start_requested',
        software_instance.getSlapState())
    self.assertEqual('validated',
        software_instance.getValidationState())

  def test_SoftwareInstance_tryToGarbageCollect_script_notArchived(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'destroy_requested')

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollect()
    self.assertEqual('start_requested',
        software_instance.getSlapState())
    self.assertEqual('validated',
        software_instance.getValidationState())

  def test_SoftwareInstance_tryToGarbageCollect_script_unlinkedSuccessor(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance_tree.edit(successor_list=[])
    instance_tree.archive()
    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'destroy_requested')

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollect()

    self.assertEqual('destroy_requested',
        software_instance.getSlapState())
    self.assertEqual('validated',
        software_instance.getValidationState())


class TestSlapOSUpdateComputeNodeCapacityScopeAlarm(SlapOSTestCaseMixin):
  #################################################################
  # slapos_update_compute_node_capacity_scope
  #################################################################
  def test_ComputeNode_checkAndUpdateCapacityScope_alarm_open(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(
      allocation_scope='open',
    )
    self._test_alarm(
      self.portal.portal_alarms.slapos_update_compute_node_capacity_scope,
      compute_node,
      'ComputeNode_checkAndUpdateCapacityScope'
    )

  def test_ComputeNode_checkAndUpdateCapacityScope_alarm_close(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(
      allocation_scope='close',
    )
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_update_compute_node_capacity_scope,
      compute_node,
      'ComputeNode_checkAndUpdateCapacityScope'
    )

  def test_ComputeNode_checkAndUpdateCapacityScope_alarm_invalidated(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.invalidate()
    compute_node.edit(
      allocation_scope='open',
    )
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_update_compute_node_capacity_scope,
      compute_node,
      'ComputeNode_checkAndUpdateCapacityScope'
    )

  #################################################################
  # ComputeNode_checkAndUpdateCapacityScope
  #################################################################
  def test_ComputeNode_checkAndUpdateCapacityScope_script_open(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    compute_node.setAccessStatus("#access ok")
    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('open', compute_node.getCapacityScope())

  def addComputerModel(self, capacity_quantity=None):
    return self.portal.computer_model_module.newContent(
      portal_type="Computer Model",
      reference='TESTCM-%s' % self.generateNewId(),
      capacity_quantity=capacity_quantity
    )

  def test_ComputeNode_checkAndUpdateCapacityScope_script_model(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    compute_node.setAccessStatus("#access ok")

    computer_model = self.addComputerModel(9999)
    compute_node.edit(specialise_value=computer_model, capacity_quantity=None)

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('open', compute_node.getCapacityScope())
    self.assertEqual(computer_model.getCapacityQuantity(),
                     compute_node.getCapacityQuantity())

  def test_ComputeNode_checkAndUpdateCapacityScope_script_modelNoCapacity(self):
    compute_node, partition = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    compute_node.setAccessStatus("#access ok")

    computer_model = self.addComputerModel(1)
    compute_node.edit(specialise_value=computer_model, capacity_quantity=None)

    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self.tic()

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('close', compute_node.getCapacityScope())
    self.assertEqual('Compute Node capacity limit exceeded',
        compute_node.workflow_history['edit_workflow'][-1]['comment'])
    self.assertEqual(computer_model.getCapacityQuantity(),
                     compute_node.getCapacityQuantity())

  def test_ComputeNode_checkAndUpdateCapacityScope_script_modelHasCapacity(self):
    # If capacity is set on compute_node, model value is ignored.
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    compute_node.setAccessStatus("#access ok")

    computer_model = self.addComputerModel(1)
    compute_node.edit(specialise_value=computer_model, capacity_quantity=2)

    self.tic()

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('open', compute_node.getCapacityScope())
    self.assertEqual(2,
                     compute_node.getCapacityQuantity())


  def test_ComputeNode_checkAndUpdateCapacityScope_script_withOldAccess(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    try:
      self.pinDateTime(addToDate(DateTime(),to_add={'minute': -11}))
      compute_node.setAccessStatus("#access ok")
    finally:
      self.unpinDateTime()

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('close', compute_node.getCapacityScope())
    self.assertEqual("Compute Node didn't contact for more than 10 minutes",
        compute_node.workflow_history['edit_workflow'][-1]['comment'])

  def test_ComputeNode_checkAndUpdateCapacityScope_script_noCapacityQuantity(self):
    compute_node, partition = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    compute_node.setAccessStatus("#access ok")
    compute_node.edit(capacity_quantity=1)

    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self.tic()

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('close', compute_node.getCapacityScope())
    self.assertEqual('Compute Node capacity limit exceeded',
        compute_node.workflow_history['edit_workflow'][-1]['comment'])

  def test_ComputeNode_checkAndUpdateCapacityScope_script_noAccess(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('close', compute_node.getCapacityScope())
    self.assertEqual("Compute Node didn't contact the server",
        compute_node.workflow_history['edit_workflow'][-1]['comment'])

  def test_ComputeNode_checkAndUpdateCapacityScope_script_capacityClose(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.setAccessStatus("#access ok")
    compute_node.edit(capacity_scope='close')

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('open', compute_node.getCapacityScope())

  def test_ComputeNode_checkAndUpdateCapacityScope_script_withError(self):
    compute_node, _ = self.addComputeNodeAndPartition()
    compute_node.edit(allocation_scope='open')
    compute_node.edit(capacity_scope='open')
    compute_node.setAccessStatus('#error not ok')

    compute_node.ComputeNode_checkAndUpdateCapacityScope()
    self.assertEqual('close', compute_node.getCapacityScope())
    self.assertEqual("Compute Node reported an error",
        compute_node.workflow_history['edit_workflow'][-1]['comment'])


class TestSlapOSGarbageCollectStoppedRootTreeAlarm(SlapOSTestCaseMixin):
  #################################################################
  # slapos_stop_collect_instance
  #################################################################
  def test_SoftwareInstance_tryToStopCollect_alarm(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self._test_alarm(
      self.portal.portal_alarms.slapos_stop_collect_instance,
      software_instance,
      'SoftwareInstance_tryToStopCollect'
    )

  def test_SoftwareInstance_tryToStopCollect_alarm_invalidated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.invalidate()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_stop_collect_instance,
      software_instance,
      'SoftwareInstance_tryToStopCollect'
    )

  #################################################################
  # SoftwareInstance_tryToStopCollect
  #################################################################
  def test_SoftwareInstance_tryToStopCollect_REQUEST_disallowed(self):
    self.assertRaises(
      Unauthorized,
      self.portal.SoftwareInstance_tryToStopCollect,
      REQUEST={})

  def test_SoftwareInstance_tryToStopCollect_script_startedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'stop_requested')
    self.assertEqual('start_requested', software_instance.getSlapState())

    software_instance.SoftwareInstance_tryToStopCollect()
    self.assertEqual('stop_requested', software_instance.getSlapState())

  def test_SoftwareInstance_tryToStopCollect_script_destroyedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'stop_requested')
    self.portal.portal_workflow._jumpToStateFor(software_instance,
        'destroy_requested')

    software_instance.SoftwareInstance_tryToStopCollect()
    self.assertEqual('destroy_requested', software_instance.getSlapState())

  def test_SoftwareInstance_tryToStopCollect_script_startedInstanceTree(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    self.assertEqual('start_requested', instance_tree.getSlapState())
    self.assertEqual('start_requested', software_instance.getSlapState())

    software_instance.SoftwareInstance_tryToStopCollect()
    self.assertEqual('start_requested', software_instance.getSlapState())


class TestSlapOSGarbageCollectNonAllocatedRootTreeAlarm(SlapOSTestCaseMixin):
  #################################################################
  # slapos_garbage_collect_non_allocated_root_tree
  #################################################################
  def test_tryToGarbageCollectNonAllocatedRootTree_alarm(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self._test_alarm(
      self.portal.portal_alarms.slapos_garbage_collect_non_allocated_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree'
    )

  def test_tryToGarbageCollectNonAllocatedRootTree_alarm_invalidated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.invalidate()
    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_garbage_collect_non_allocated_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree'
    )

  def test_tryToGarbageCollectNonAllocatedRootTree_alarm_allocated(self):
    _, partition = self.addComputeNodeAndPartition()
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_garbage_collect_non_allocated_root_tree,
      software_instance,
      'SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree'
    )

  #################################################################
  # SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree
  #################################################################
  def test_tryToGarbageCollectNonAllocatedRootTree_REQUEST_disallowed(self):
    self.assertRaises(
      Unauthorized,
      self.portal.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree,
      REQUEST={})

  def test_tryToGarbageCollectNonAllocatedRootTree_script_invalidatedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.invalidate()

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', software_instance.getSlapState())
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_destroyedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('destroy_requested', software_instance.getSlapState())
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_allocatedInstance(self):
    _, partition = self.addComputeNodeAndPartition()
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', software_instance.getSlapState())
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_noAllocationTryFound(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', software_instance.getSlapState())
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_recentAllocationTryFound(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.workflow_history['edit_workflow'] = [{
        'comment':'Allocation failed: no free Compute Partition',
        'error_message': '',
        'actor': 'ERP5TypeTestCase',
        'state': 'current',
        'time': addToDate(DateTime(), to_add={'day': -2}),
        'action': 'edit'
    }]

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', software_instance.getSlapState())
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_oldAllocationTryFound(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.tic()

    software_instance.workflow_history['edit_workflow'] = [{
        'comment':'Allocation failed: no free Compute Partition',
        'error_message': '',
        'actor': 'ERP5TypeTestCase',
        'state': 'current',
        'time': addToDate(DateTime(), to_add={'day': -4}),
        'action': 'edit'
    }]
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('destroy_requested', instance_tree.getSlapState())
    self.assertEqual('archived', instance_tree.getValidationState())
    self.assertEqual('start_requested', software_instance.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_recentAllocationTryFoundAllocationDisallowed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    software_instance.workflow_history['edit_workflow'] = [{
        'comment':'Allocation failed: Allocation disallowed',
        'error_message': '',
        'actor': 'ERP5TypeTestCase',
        'state': 'current',
        'time': addToDate(DateTime(), to_add={'day': -2}),
        'action': 'edit'
    }]

    self.tic()
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', software_instance.getSlapState())
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_oldAllocationTryFoundAllocationDisallowed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.tic()

    software_instance.workflow_history['edit_workflow'] = [{
        'comment':'Allocation failed: Allocation disallowed',
        'error_message': '',
        'actor': 'ERP5TypeTestCase',
        'state': 'current',
        'time': addToDate(DateTime(), to_add={'day': -4}),
        'action': 'edit'
    }]
    software_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('destroy_requested', instance_tree.getSlapState())
    self.assertEqual('archived', instance_tree.getValidationState())
    self.assertEqual('start_requested', software_instance.getSlapState())


  def test_tryToGarbageCollectNonAllocatedRootTree_script_complexTree(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    request_kw = dict(
      software_release=\
          self.generateNewSoftwareReleaseUrl(),
      software_type=self.generateNewSoftwareType(),
      instance_xml=self.generateSafeXml(),
      sla_xml=self.generateSafeXml(),
      shared=False,
      software_title="another %s" % instance_tree.getTitle(),
      state='started'
    )
    software_instance.requestInstance(**request_kw)
    sub_instance = software_instance.getSuccessorValue()
    self.tic()
    sub_instance.workflow_history['edit_workflow'] = [{
        'comment':'Allocation failed: no free Compute Partition',
        'error_message': '',
        'actor': 'ERP5TypeTestCase',
        'state': 'current',
        'time': addToDate(DateTime(), to_add={'day': -4}),
        'action': 'edit'
    }]

    sub_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', instance_tree.getSlapState())

  def test_tryToGarbageCollectNonAllocatedRootTree_script_complexTreeAllocationDisallowed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    request_kw = dict(
      software_release=\
          self.generateNewSoftwareReleaseUrl(),
      software_type=self.generateNewSoftwareType(),
      instance_xml=self.generateSafeXml(),
      sla_xml=self.generateSafeXml(),
      shared=False,
      software_title="another %s" % instance_tree.getTitle(),
      state='started'
    )
    software_instance.requestInstance(**request_kw)
    sub_instance = software_instance.getSuccessorValue()
    self.tic()
    sub_instance.workflow_history['edit_workflow'] = [{
        'comment':'Allocation failed: Allocation disallowed',
        'error_message': '',
        'actor': 'ERP5TypeTestCase',
        'state': 'current',
        'time': addToDate(DateTime(), to_add={'day': -2}),
        'action': 'edit'
    }]

    sub_instance.SoftwareInstance_tryToGarbageCollectNonAllocatedRootTree()
    self.assertEqual('start_requested', instance_tree.getSlapState())


class TestSlapOSGarbageCollectUnlinkedInstanceAlarm(SlapOSTestCaseMixin):

  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()
    self.assertEqual(instance.getSpecialiseValue().getRelativeUrl(),
                    sub_instance.getSpecialise())  
    return sub_instance

  #################################################################
  # slapos_garbage_collect_destroy_unlinked_instance
  #################################################################
  def test_SoftwareInstance_tryToGarbageUnlinkedInstance_alarm_inlinkedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance0 = self.doRequestInstance(software_instance, 'instance0')
    self.assertEqual(software_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
    software_instance.edit(successor_list=[])
    self._test_alarm(
      self.portal.portal_alarms.slapos_garbage_collect_destroy_unlinked_instance,
      instance0,
      'SoftwareInstance_tryToGarbageUnlinkedInstance'
    )

  def test_SoftwareInstance_tryToGarbageUnlinkedInstance_alarm_inlinkedInstanceSlave(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    instance0 = self.doRequestInstance(software_instance, 'instance0', True)
    self.assertEqual(software_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
    software_instance.edit(successor_list=[])
    self._test_alarm(
      self.portal.portal_alarms.slapos_garbage_collect_destroy_unlinked_instance,
      instance0,
      'SoftwareInstance_tryToGarbageUnlinkedInstance'
    )

  #################################################################
  # SoftwareInstance_tryToGarbageUnlinkedInstance
  #################################################################
  def test_SoftwareInstance_tryToGarbageUnlinkedInstance_script(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    instance0 = self.doRequestInstance(software_instance, 'instance0')
    self.assertEqual(instance0.getSuccessorRelatedTitle(), software_instance.getTitle())
    # Remove successor link
    software_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_script_hostingDestroyed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    instance0 = self.doRequestInstance(software_instance, 'instance0')
    self.assertEqual(instance0.getSuccessorRelatedTitle(), software_instance.getTitle())
    # Remove successor link
    software_instance.edit(successor_list=[])
    self.tic()
    self.assertEqual(instance0.getSuccessorRelatedTitle(), None)

    instance_tree.archive()
    self.portal.portal_workflow._jumpToStateFor(instance_tree,
        'destroy_requested')
    self.portal.portal_workflow._jumpToStateFor(software_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_script_willUnlinkChildren(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    instance0 = self.doRequestInstance(software_instance, 'instance0')
    self.assertEqual(instance0.getSuccessorRelatedTitle(), software_instance.getTitle())
    instance_instance0 = self.doRequestInstance(instance0, 'Subinstance0')
    self.assertEqual(instance_instance0.getSuccessorRelatedTitle(),
                     'instance0')
    # Remove successor link
    software_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_script_willDelay(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    instance0 = self.doRequestInstance(software_instance, 'instance0')
    self.assertEqual(instance0.getSuccessorRelatedTitle(), software_instance.getTitle())
    instance_instance0 = self.doRequestInstance(instance0, 'Subinstance0')
    self.assertEqual(instance_instance0.getSuccessorRelatedTitle(),
                     'instance0')
    # Remove successor link
    software_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_script_unlinkedRoot(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    # Remove successor link
    instance_tree.edit(successor_list=[])
    self.tic()
    self.assertEqual(software_instance.getSuccessorRelatedTitle(), None)
    # will not destroy
    self.assertRaises(
      ValueError,
      software_instance.SoftwareInstance_tryToGarbageUnlinkedInstance,
      delay_time=-10)
    self.tic()
    self.assertEqual(software_instance.getSlapState(), 'start_requested')

  def test_SoftwareInstance_tryToGarbageUnlinkedInstance_script_notUnlinked(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    instance0 = self.doRequestInstance(software_instance, 'instance0')
    self.assertEqual(instance0.getSuccessorRelatedTitle(), software_instance.getTitle())

    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')



class TestSlapOSInvalidateDestroyedInstance(SlapOSTestCaseMixin):
  #################################################################
  # slapos_cloud_invalidate_destroyed_instance
  #################################################################
  def test_tryToInvalidateIfDestroyed_alarm_softwareInstanceInvalidated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    self.portal.portal_workflow._jumpToStateFor(software_instance, 'invalidated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')
    self.tic()

    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_cloud_invalidate_destroyed_instance,
      software_instance,
      'SoftwareInstance_tryToInvalidateIfDestroyed'
    )

  def test_tryToInvalidateIfDestroyed_alarm_softwareInstanceMatching(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    self.portal.portal_workflow._jumpToStateFor(software_instance, 'validated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')
    self.tic()

    self._test_alarm(
      self.portal.portal_alarms.slapos_cloud_invalidate_destroyed_instance,
      software_instance,
      'SoftwareInstance_tryToInvalidateIfDestroyed'
    )

  def test_tryToInvalidateIfDestroyed_alarm_softwareInstanceAllocated(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())
    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self.portal.portal_workflow._jumpToStateFor(software_instance, 'validated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')
    self.tic()

    self._test_alarm_not_visited(
      self.portal.portal_alarms.slapos_cloud_invalidate_destroyed_instance,
      software_instance,
      'SoftwareInstance_tryToInvalidateIfDestroyed'
    )

  #################################################################
  # SoftwareInstance_tryToInvalidateIfDestroyed
  #################################################################
  def test_tryToInvalidateIfDestroyed_REQUEST_disallowed(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.assertRaises(
      Unauthorized,
      software_instance.SoftwareInstance_tryToInvalidateIfDestroyed,
      REQUEST={})

  def test_tryToInvalidateIfDestroyed_script_unexpectedContext(self):
    self.assertRaises(
      TypeError,
      self.portal.SoftwareInstance_tryToInvalidateIfDestroyed,
      )

  def test_tryToInvalidateIfDestroyed_script_expectedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'validated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')
    software_instance.SoftwareInstance_tryToInvalidateIfDestroyed()
    self.assertEqual(software_instance.getValidationState(), "invalidated")
    self.assertEqual(software_instance.getSlapState(), "destroy_requested")

  def test_tryToInvalidateIfDestroyed_script_invalidatedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'invalidated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')
    software_instance.SoftwareInstance_tryToInvalidateIfDestroyed()
    self.assertEqual(software_instance.getValidationState(), "invalidated")
    self.assertEqual(software_instance.getSlapState(), "destroy_requested")

  def test_tryToInvalidateIfDestroyed_script_notDestroyedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'validated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'stop_requested')
    software_instance.SoftwareInstance_tryToInvalidateIfDestroyed()
    self.assertEqual(software_instance.getValidationState(), "validated")
    self.assertEqual(software_instance.getSlapState(), "stop_requested")

  def test_tryToInvalidateIfDestroyed_script_allocatedInstance(self):
    instance_tree = self.addInstanceTree()
    software_instance = instance_tree.getSuccessorValue()

    _, partition = self.addComputeNodeAndPartition(project=instance_tree.getFollowUpValue())
    software_instance.setAggregateValue(partition)
    partition.markBusy()

    self.portal.portal_workflow._jumpToStateFor(software_instance, 'validated')
    self.portal.portal_workflow._jumpToStateFor(software_instance, 'destroy_requested')
    software_instance.SoftwareInstance_tryToInvalidateIfDestroyed()
    self.assertEqual(software_instance.getValidationState(), "validated")
    self.assertEqual(software_instance.getSlapState(), "destroy_requested")

