class Test(unittest.TestCase):
    def setUp(self):
        globallock._lock.acquire()
        self._hosts = Hosts()
        self.tested = FreePool(self._hosts)

    def tearDown(self):
        globallock._lock.release()

    def test_OneHost(self):
        host = HostStateMachine('host1')
        self.tested.put(host)
        self.assertIn(host, self.tested.all())
        self.tested.takeOut(host)
        self.assertNotIn(host, self.tested.all())

    def test_DestroyCallback(self):
        host = HostStateMachine('host1')
        self._hosts.add(host)
        self.tested.put(host)
        host._destroyCallback(host)
        self.assertNotIn(host, self.tested.all())

    def test_All(self):
        hosts = [HostStateMachine(str(i)) for i in xrange(10)]
        for host in hosts:
            self._hosts.add(host)

        for host in hosts:
            self.tested.put(host)

        actualAll = self.tested.all()
        self.assertEquals(set(actualAll), set(hosts))
 def setUp(self):
     self.addCleanup(globallock._lock.release)
     globallock._lock.acquire()
     self.currentTimer = None
     self.currentTimerTag = None
     timer.scheduleIn = self.scheduleTimerIn
     timer.cancelAllByTag = self.cancelAllTimersByTag
     requirements = dict(node0=dict(imageHint='alpha-bravo', imageLabel='tango-lima'),
                         node1=dict(imageHint='charlie-delta', imageLabel='kilo-juliet'),
                         node2=dict(imageHint='echo-foxtrot', imageLabel='zooloo-papa'))
     self.index = random.randint(1, sys.maxint)
     self.allocationInfo = 'This allocation has got swag.'
     allocated = dict((hostName, HostStateMachine(Host(hostName)))
                      for hostName in requirements.keys())
     self.originalAllocated = copy.copy(allocated)
     self.expectedStates = dict(allocatedButNotInaugurated=set(allocated.values()),
                                inaugurated=set())
     self.expectedDestroyed = set()
     self.expectedDetached = set()
     self.expectedReleased = set()
     self.wasAllocationDoneAtSomePoint = False
     self.broadcaster.reset_mock()
     self.hosts = Hosts()
     self.freepool = FreePool(self.hosts)
     for stateMachine in allocated.values():
         self.hosts.add(stateMachine)
     self.tested = allocation.Allocation(self.index, requirements, self.allocationInfo, allocated,
                                         self.broadcaster, self.freepool, self.hosts)
 def setUp(self):
     self.broadcaster.reset_mock()
     hostNames = ["alpha", "bravo", "charlie", "delta"]
     self.hosts = Hosts()
     self.freePool = freepool.FreePool(self.hosts)
     self.allocationInfo = dict(purpose='forfun', nice=0)
     for hostName in hostNames:
         stateMachine = HostStateMachine(Host(hostName))
         self.hosts.add(stateMachine)
         self.freePool.put(stateMachine)
     self.tested = Allocations(self.broadcaster, self.hosts, self.freePool, self.osmosisServer)
     self.expectedCreationBroadcasts = list()
     self.expectedRequestBroadcasts = list()
     self.expectedRejectedBroadcasts = list()
 def setUp(self):
     globallock._lock.acquire()
     self.broadcaster = mock.Mock()
     hostNames = ["alpha", "bravo", "charlie", "delta"]
     self.hosts = Hosts()
     self.freePool = FreePool(self.hosts)
     self.osmosisServer = 'what-a-cool-osmosis-server'
     self.allocationInfo = dict(purpose='forfun', nice=0)
     timer.scheduleIn = mock.Mock()
     timer.cancelAllByTag = mock.Mock()
     self.currentTimer = None
     self.currentTimerTag = None
     for hostName in hostNames:
         stateMachine = HostStateMachine(Host(hostName))
         self.hosts.add(stateMachine)
         self.freePool.put(stateMachine)
     self.tested = Allocations(self.broadcaster, self.hosts, self.freePool, self.osmosisServer)
     self.requirements = dict(node0=dict(imageLabel="echo-foxtrot", imageHint="golf"),
                              node1=dict(imageLabel="hotel-india", imageHint="juliet"))
 def setUp(self):
     globallock._lock.acquire()
     self._hosts = Hosts()
     self.tested = FreePool(self._hosts)
class Test(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.broadcaster = Publish()

    def setUp(self):
        self.addCleanup(globallock._lock.release)
        globallock._lock.acquire()
        self.currentTimer = None
        self.currentTimerTag = None
        timer.scheduleIn = self.scheduleTimerIn
        timer.cancelAllByTag = self.cancelAllTimersByTag
        requirements = dict(node0=dict(imageHint='alpha-bravo', imageLabel='tango-lima'),
                            node1=dict(imageHint='charlie-delta', imageLabel='kilo-juliet'),
                            node2=dict(imageHint='echo-foxtrot', imageLabel='zooloo-papa'))
        self.index = random.randint(1, sys.maxint)
        self.allocationInfo = 'This allocation has got swag.'
        allocated = dict((hostName, HostStateMachine(Host(hostName)))
                         for hostName in requirements.keys())
        self.originalAllocated = copy.copy(allocated)
        self.expectedStates = dict(allocatedButNotInaugurated=set(allocated.values()),
                                   inaugurated=set())
        self.expectedDestroyed = set()
        self.expectedDetached = set()
        self.expectedReleased = set()
        self.wasAllocationDoneAtSomePoint = False
        self.broadcaster.reset_mock()
        self.hosts = Hosts()
        self.freepool = FreePool(self.hosts)
        for stateMachine in allocated.values():
            self.hosts.add(stateMachine)
        self.tested = allocation.Allocation(self.index, requirements, self.allocationInfo, allocated,
                                            self.broadcaster, self.freepool, self.hosts)

    def test_Index(self):
        self.assertEquals(self.tested.index(), self.index)

    def test_AllocationInfo(self):
        self.assertEquals(self.allocationInfo, self.tested.allocationInfo())

    def test_InauguratedWhenNotDoneRaisesAssertionError(self):
        self.assertRaises(AssertionError, self.tested.inaugurated)

    def test_InauguratedWhenDone(self):
        self.fakeInaugurationDoneForAll()
        expected = {stateMachine.hostImplementation().id(): stateMachine for stateMachine in
                    self.expectedStates["inaugurated"]}
        self.assertEquals(expected, self.tested.inaugurated())

    def test_Allocated(self):
        self.assertEquals(self.originalAllocated, self.tested.allocated())

    def test_Free(self):
        self.tested.free()
        self.assertEquals(self.tested.dead(), "freed")
        self.validate()

    def test_CantFreeDeadAllocation(self):
        self.tested.free()
        self.assertEquals(self.tested.dead(), "freed")
        self.assertRaises(Exception, self.tested.free)
        self.validate()

    def test_Withdraw(self):
        self.tested.withdraw("Don't want this allocation anymore")
        self.assertEquals(self.tested.dead(), "withdrawn")
        self.validate()

    def test_HeartBeatWhenDeadDoesNothing(self):
        self.assertIsNotNone(self.currentTimer)
        self.tested.free()
        self.assertIsNone(self.currentTimer)
        self.tested.heartbeat()
        self.assertIsNone(self.currentTimer)
        self.validate()

    def test_NotDeadForAWhileIfNotDead(self):
        self.assertFalse(self.tested.deadForAWhile())

    def test_NoCrashWhenGotMoreThanOneInaugurationDoneForSameMachine(self):
        self.fakeInaugurationDoneForAll()
        self.fakeInaugurationDoneForAll()
        self.fakeInaugurationDoneForAll()
        self.fakeInaugurationDoneForAll()
        self.fakeInaugurationDoneForAll()

    def test_DeadForAWhile(self):
        self.tested.free()
        executeCodeWhileAllocationIsDeadOfHeartbeatTimeout(self.tested, lambda: None)

    def test_NotDeadForAWhile(self):
        orig_time = time.time
        self.tested.free()
        timeInWhichAllocationIsDeadNotForAWhile = \
            time.time() + self.tested._LIMBO_AFTER_DEATH_DURATION * 0.9
        try:
            time.time = mock.Mock(return_value=timeInWhichAllocationIsDeadNotForAWhile)
            self.assertFalse(self.tested.deadForAWhile())
        finally:
            time.time = orig_time

    def test_HeartBeatTimeout(self):
        self.currentTimer()
        self.assertEquals(self.tested.dead(), "heartbeat timeout")
        self.validate()

    def test_DieUponSelfDestructionOfMachine(self):
        self.destroyMachineByName('node0')
        self.assertIn("Unable to inaugurate ", self.tested.dead())
        self.validate()

    def test_NoCrashIfStateMachineSelfDestructedWhileAllocationIsDead(self):
        machine = self.originalAllocated['node0']
        destroyCallback = machine._destroyCallback
        stateChangeCallback = self.originalAllocated['node0']._destroyCallback
        self.tested.free()
        self.assertEquals(self.tested.dead(), "freed")
        self.validate()
        machine.setDestroyCallback(destroyCallback)
        self.destroyMachineByName('node0')
        machine.setDestroyCallback(None)
        self.freepool.takeOut(machine)
        # todo: fix the following (which is a bug); The state machine should unassign in case it deetroys
        # itself.
        machine.assign(stateChangeCallback, None, None)
        self.validate()

    def test_DetachHostBeforeInaugurated(self):
        machine = self.originalAllocated['node0']
        self.detachHost(machine)
        self.validate()
        self.fakeInaugurationDoneForAll()
        self.validate()

    def test_DetachHostAfterInaugurated(self):
        machine = self.originalAllocated['node0']
        self.fakeInaugurationDoneForAll()
        self.validate()
        self.detachHost(machine)
        self.validate()

    def test_CannotDetachHostAfterDestroyed(self):
        machine = self.originalAllocated['node0']
        self.destroyMachineByName('node0')
        self.validate()
        self.assertRaises(Exception, self.tested.detachHost, machine)
        self.validate()

    def test_CannotDetachHostAfterInauguratedAndDestroyed(self):
        machine = self.originalAllocated['node0']
        self.fakeInaugurationDoneForAll()
        self.assertIn(machine, self.tested.allocated().values())
        self.destroyMachineByName('node0')
        self.validate()
        self.assertRaises(Exception, self.tested.detachHost, machine)
        self.validate()

    def test_CannotDetachUnAllocatedHost(self):
        machine = HostStateMachine("whatIsThisMachine")
        self.assertRaises(Exception, self.detachHost, machine)

    def test_DetachingLastHostKillsAllocation(self):
        for machine in self.originalAllocated.values():
            self.detachHost(machine)
            self.validate()
        isDead = self.tested.dead() is not None
        self.assertTrue(isDead)

    def test_ReleaseHostBeforeInaugurated(self):
        machine = self.originalAllocated['node0']
        self.releaseHost(machine)
        self.validate()
        self.fakeInaugurationDoneForAll()
        self.validate()

    def test_ReleaseHostAfterInaugurated(self):
        machine = self.originalAllocated['node0']
        self.fakeInaugurationDoneForAll()
        self.validate()
        self.releaseHost(machine)
        self.validate()

    def test_CannotRelaseHostAfterDestroyed(self):
        machine = self.originalAllocated['node0']
        self.destroyMachineByName('node0')
        self.validate()
        self.assertRaises(Exception, self.tested.releaseHost, machine)
        self.validate()

    def test_CannotReleaseHostAfterInauguratedAndDestroyed(self):
        machine = self.originalAllocated['node0']
        self.fakeInaugurationDoneForAll()
        self.assertIn(machine, self.tested.allocated().values())
        self.destroyMachineByName('node0')
        self.validate()
        self.assertRaises(Exception, self.tested.releaseHost, machine)
        self.validate()

    def test_CannotReleaseUnAllocatedHost(self):
        machine = HostStateMachine("whatIsThisMachine")
        self.assertRaises(Exception, self.releaseHost, machine)

    def test_ReleasingLastHostKillsAllocation(self):
        for machine in self.originalAllocated.values():
            self.releaseHost(machine)
            self.validate()
        isDead = self.tested.dead() is not None
        self.assertTrue(isDead)

    def test_CannotReleaseDetachedHost(self):
        machine = self.originalAllocated['node0']
        self.detachHost(machine)
        self.assertRaises(Exception, self.releaseHost, machine)

    def test_CannotDetachReleasedHost(self):
        machine = self.originalAllocated['node0']
        self.releaseHost(machine)
        self.assertRaises(Exception, self.detachHost, machine)

    def test_AllocationCreationReported(self):
        self.validateBroadcastCalls()

    def test_AllocationDoneBroadcasted(self):
        self.fakeInaugurationDoneForAll()
        self.validateBroadcastCalls()

    def test_AllocationDeathBroadcasted(self):
        self.fakeInaugurationDoneForAll()
        self.tested.free()
        self.validateBroadcastCalls()

    def fakeInaugurationDoneForAll(self):
        self.wasAllocationDoneAtSomePoint = True
        collection = self.expectedStates["allocatedButNotInaugurated"]
        for stateMachine in self.originalAllocated.values():
            stateMachine.fakeInaugurationDone()
            if stateMachine in collection:
                collection.remove(stateMachine)
                self.expectedStates["inaugurated"].add(stateMachine)

    def scheduleTimerIn(self, timeout, callback, tag):
        self.assertIs(self.currentTimer, None)
        self.assertIs(self.currentTimerTag, None)
        self.currentTimer = callback
        self.currentTimerTag = tag

    def cancelAllTimersByTag(self, tag):
        if self.currentTimerTag is not None:
            self.assertIsNot(self.currentTimer, None)
            self.assertIs(self.currentTimerTag, tag)
        self.currentTimer = None
        self.currentTimerTag = None

    def _validateFreePool(self):
        isDead = self.tested.dead() is not None
        expectedHostsInFreePool = set()
        if isDead:
            expectedHostsInFreePool = self.expectedStates["allocatedButNotInaugurated"].union(
                self.expectedStates["inaugurated"]).union(self.expectedReleased)
        else:
            expectedHostsInFreePool = self.expectedReleased
        expectedHostsInFreePool = [host for host in expectedHostsInFreePool if host not in
                                   self.expectedDestroyed]
        for stateMachine in expectedHostsInFreePool:
            self.assertIn(stateMachine, self.freepool.all())
        expectedHostsNotInFreePool = [stateMachine for stateMachine in self.originalAllocated.values() if
                                      stateMachine not in expectedHostsInFreePool]
        for stateMachine in expectedHostsNotInFreePool:
            self.assertNotIn(stateMachine, self.freepool.all())

    def _validateAllocated(self):
        actual = self.tested.allocated()
        expected = self.expectedStates["allocatedButNotInaugurated"]
        isDead = self.tested.dead() is not None
        if not isDead:
            expected = expected.union(self.expectedStates["inaugurated"])
        expected = {stateMachine.hostImplementation().id(): stateMachine for stateMachine in expected}
        self.assertEquals(expected, actual)

    def _validateInaugurated(self):
        isDead = self.tested.dead() is not None
        if isDead:
            self.assertRaises(AssertionError, self.tested.inaugurated)
        else:
            if self.tested.done():
                expected = {stateMachine.hostImplementation().id(): stateMachine for stateMachine in
                            self.expectedStates["inaugurated"]}
                self.assertEquals(self.tested.inaugurated(), expected)
            else:
                self.assertRaises(AssertionError, self.tested.inaugurated)

    def validateBroadcastCalls(self):
        expectedMethodsNames = list()
        expectedMethodsNames.append("allocationCreated")
        if self.wasAllocationDoneAtSomePoint:
            expectedMethodsNames.append("allocationDone")
        deathReason = self.tested.dead()
        if deathReason is not None:
            expectedMethodsNames.append("allocationDied")
            expectedMethodsNames.append("cleanupAllocationPublishResources")
        actualCalls = \
            [call for call in self.broadcaster.method_calls if call[0] != "allocationProviderMessage"]
        while expectedMethodsNames:
            expectedMethodName = expectedMethodsNames.pop(0)
            actualCall = actualCalls.pop(0)
            actualMethodName = actualCall[0]
            args = actualCall[1]
            kwargs = actualCall[2]
            if expectedMethodName == "allocationCreated":
                allocationID = kwargs["allocationID"]
                expectedAllocate = {name: self.originalAllocated[name].hostImplementation().id()
                                    for name in self.originalAllocated}
            elif expectedMethodName == "allocationDone":
                allocationID = args[0]
            elif expectedMethodName == "allocationDied":
                allocationID = args[0]
                reason = kwargs["reason"]
                self.assertEquals(self.tested.dead(), reason)
            elif expectedMethodName == "cleanupAllocationPublishResources":
                allocationID = args[0]
            else:
                self.assertTrue(False)
            self.assertEquals(allocationID, self.tested.index())
            self.assertEquals(actualMethodName, expectedMethodName)
        self.assertFalse(actualCalls)

    def _validateDone(self):
        isDead = self.tested.dead() is not None
        if isDead:
            self.assertRaises(AssertionError, self.tested.done)
        else:
            allowedNotToBeInaugurated = self.expectedDetached
            expected = \
                self.expectedStates["allocatedButNotInaugurated"].issubset(allowedNotToBeInaugurated)
            actual = self.tested.done()
            self.assertEquals(expected, actual)

    def _validateHosts(self):
        hosts = self.hosts.all()
        notExpected = self.expectedDestroyed.union(self.expectedDetached)
        for host in notExpected:
            self.assertNotIn(host, hosts)
        expected = [host for host in self.originalAllocated.values() if host not in notExpected]
        for host in expected:
            self.assertIn(host, hosts)

    def validateAllocationIsNotEmpty(self):
        self.assertTrue(self.tested.allocated())

    def validate(self):
        self._validateAllocated()
        self._validateInaugurated()
        self._validateFreePool()
        self._validateHosts()
        self._validateDone()
        isDead = self.tested.dead() is not None
        if not isDead:
            self.validateAllocationIsNotEmpty()
        self.validateBroadcastCalls()

    def destroyMachineByName(self, name):
        stateMachine = self.originalAllocated[name]
        stateMachine.destroy()
        self.expectedDestroyed.add(stateMachine)

    def detachHost(self, machine):
        self.tested.detachHost(machine)
        self._removeFromCollections(machine)
        self.expectedDetached.add(machine)

    def releaseHost(self, machine):
        self.tested.releaseHost(machine)
        self._removeFromCollections(machine)
        self.expectedReleased.add(machine)

    def _removeFromCollections(self, machine):
        for collection in self.expectedStates.values():
            if machine in collection:
                collection.remove(machine)
class Test(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        globallock._lock.acquire()
        self.broadcaster = Publish()
        timer.scheduleIn = mock.Mock()
        timer.cancelAllByTag = mock.Mock()
        self.requirements = dict(node0=dict(imageLabel="echo-foxtrot", imageHint="golf"),
                                 node1=dict(imageLabel="hotel-india", imageHint="juliet"))
        self.currentTimer = None
        self.currentTimerTag = None
        self.osmosisServer = 'what-a-cool-osmosis-server'

    def setUp(self):
        self.broadcaster.reset_mock()
        hostNames = ["alpha", "bravo", "charlie", "delta"]
        self.hosts = Hosts()
        self.freePool = freepool.FreePool(self.hosts)
        self.allocationInfo = dict(purpose='forfun', nice=0)
        for hostName in hostNames:
            stateMachine = HostStateMachine(Host(hostName))
            self.hosts.add(stateMachine)
            self.freePool.put(stateMachine)
        self.tested = Allocations(self.broadcaster, self.hosts, self.freePool, self.osmosisServer)
        self.expectedCreationBroadcasts = list()
        self.expectedRequestBroadcasts = list()
        self.expectedRejectedBroadcasts = list()

    @classmethod
    def tearDownClass(self):
        globallock._lock.release()

    def test_Create(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        self.assertEquals(_allocation, self.tested.byIndex(_allocation.index()))
        self.assertEquals(self.allocationInfo, _allocation.allocationInfo())

    def test_AllocationCreationFails(self):
        origAllocation = allocation.Allocation
        try:
            allocation.Allocation = mock.Mock(side_effect=ValueError("don't wanna"))
            self.assertRaises(ValueError, self.createAllocation, self.requirements, self.allocationInfo)
        finally:
            allocation.Allocation = origAllocation

    def test_NoSuchAllocation(self):
        self.assertRaises(IndexError, self.tested.byIndex, 1)

    def test_OsmosisListLabelsReturnsAnotherLabel(self):
        abused = False

        def anotherLabelMock(cmd):
            if cmd[0:2] == ['osmosis', 'listlabels']:
                return cmd[2] + "_not"
            abused = True
        self.assertRaises(Exception, self.createAllocation, self.requirements, self.allocationInfo,
                          listLabelsMock=anotherLabelMock)
        self.assertFalse(abused)

    def test_CreateCleansUp(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        _allocation.free()

        def createCallback():
            self.createAllocation(self.requirements, self.allocationInfo)

        executeCodeWhileAllocationIsDeadOfHeartbeatTimeout(_allocation, createCallback)
        self.assertNotIn(_allocation, self.tested.all())

    def test_byIndexCleansUp(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        _allocation.free()
        idx = _allocation.index()
        self.assertRaises(IndexError, executeCodeWhileAllocationIsDeadOfHeartbeatTimeout, _allocation,
                          lambda: self.tested.byIndex(idx))

    def test_AllCleansUp(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        _allocation.free()

        def validateNotInAll():
            self.assertNotIn(_allocation, self.tested.all())
        executeCodeWhileAllocationIsDeadOfHeartbeatTimeout(_allocation, validateNotInAll)

    def test_All(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        self.assertEquals(self.tested.all(), [_allocation])

    def test_RequestBroadcasted(self):
        self.createAllocation(self.requirements, self.allocationInfo)
        self.validateRequestBroadcasts()

    def test_CreationBroadcasted(self):
        self.createAllocation(self.requirements, self.allocationInfo)
        self.validateCreationBroadcasts()

    def test_OutOfResourcesBroadcasted(self):
        tooBigOfRequirements = dict(self.requirements,
                                    node3=dict(imageLabel="kilo-lima", imageHint="mike"),
                                    node4=dict(imageLabel="november-oscar", imageHint="papa"),
                                    node5=dict(imageLabel="quebec-romeo", imageHint="sierra"))
        try:
            self.createAllocation(tooBigOfRequirements, self.allocationInfo, outOfResourcesExpected=True)
        except priority.OutOfResourcesError:
            pass
        self.expectedRejectedBroadcasts.append(dict(reason="noResources"))
        self.validateRejectedBroadcasts()

    def test_OutOfResourcesRaisedWhenOutOfResources(self):
        tooBigOfRequirements = dict(self.requirements,
                                    node3=dict(imageLabel="kilo-lima", imageHint="mike"),
                                    node4=dict(imageLabel="november-oscar", imageHint="papa"),
                                    node5=dict(imageLabel="quebec-romeo", imageHint="sierra"))
        self.assertRaises(priority.OutOfResourcesError,
                          self.createAllocation,
                          tooBigOfRequirements,
                          self.allocationInfo,
                          outOfResourcesExpected=True)

    def test_OutOfResourcesReturnsHostsTooFreePool(self):
        tooBigOfRequirements = dict(self.requirements,
                                    node3=dict(imageLabel="kilo-lima", imageHint="mike"),
                                    node4=dict(imageLabel="november-oscar", imageHint="papa"),
                                    node5=dict(imageLabel="quebec-romeo", imageHint="sierra"))
        nrFreeHostsBefore = len(list(self.freePool.all()))
        self.assertRaises(priority.OutOfResourcesError,
                          self.createAllocation,
                          tooBigOfRequirements,
                          self.allocationInfo,
                          outOfResourcesExpected=True)
        self.assertEquals(len(list(self.freePool.all())), nrFreeHostsBefore)

    def test_AllocationCreationErrorBroadcasted(self):
        origAllocation = allocation.Allocation

        class IgnoreMe(Exception):
            pass

        try:
            allocation.Allocation = mock.Mock(side_effect=IgnoreMe)
            self.createAllocation(self.requirements, self.allocationInfo)
        except IgnoreMe:
            pass
        finally:
            allocation.Allocation = origAllocation
        self.expectedRejectedBroadcasts.append(dict(reason="unknown"))
        self.validateRejectedBroadcasts()

    def test_LabelDoesNotExistInObjectStoreBroadcasted(self):

        def noLabelMock(cmd):
            return ""
        self.assertRaises(Exception, self.createAllocation, self.requirements, self.allocationInfo,
                          listLabelsMock=noLabelMock)
        self.expectedRejectedBroadcasts.append(dict(reason="labelDoesNotExist"))
        self.validateRejectedBroadcasts()

    def createAllocation(self, requirements, allocationInfo, listLabelsMock=osmosisListLabelsFoundMock,
                         outOfResourcesExpected=False):
        origRun = sh.run
        nrFreeHostsBefore = len(list(self.freePool.all()))
        self.expectedRequestBroadcasts.append((requirements, allocationInfo))
        if len(requirements) > len(list(self.freePool.all())):
            self.assertTrue(outOfResourcesExpected)
        try:
            sh.run = listLabelsMock
            _allocation = self.tested.create(requirements, self.allocationInfo)
            self.expectedCreationBroadcasts.append(dict(allocationID=_allocation.index(),
                                                        allocated=_allocation.allocated()))
        except priority.OutOfResourcesError:
            raise
        finally:
            sh.run = origRun
        self.assertEquals(len(list(self.freePool.all())), nrFreeHostsBefore - len(requirements))
        return _allocation

    def validateRequestBroadcasts(self):
        for idx, expectedCall in enumerate(self.expectedRequestBroadcasts):
            actualCall = self.broadcaster.allocationRequested.call_args_list[idx][0]
            self.assertEquals(expectedCall, actualCall)
        self.assertEquals(len(self.expectedRequestBroadcasts),
                          len(self.broadcaster.allocationRequested.call_args_list))

    def validateCreationBroadcasts(self):
        for idx, expectedCall in enumerate(self.expectedCreationBroadcasts):
            actualCall = self.broadcaster.allocationCreated.call_args_list[idx][1]
            self.assertEquals(expectedCall, actualCall)
        self.assertEquals(len(self.expectedCreationBroadcasts),
                          len(self.broadcaster.allocationCreated.call_args_list))

    def validateRejectedBroadcasts(self):
        for idx, expectedCall in enumerate(self.expectedRejectedBroadcasts):
            actualCall = self.broadcaster.allocationRejected.call_args_list[idx][1]
            self.assertEquals(expectedCall, actualCall)
        self.assertEquals(len(self.expectedRejectedBroadcasts),
                          len(self.broadcaster.allocationRejected.call_args_list))
class Test(unittest.TestCase):
    def setUp(self):
        globallock._lock.acquire()
        self.broadcaster = mock.Mock()
        hostNames = ["alpha", "bravo", "charlie", "delta"]
        self.hosts = Hosts()
        self.freePool = FreePool(self.hosts)
        self.osmosisServer = 'what-a-cool-osmosis-server'
        self.allocationInfo = dict(purpose='forfun', nice=0)
        timer.scheduleIn = mock.Mock()
        timer.cancelAllByTag = mock.Mock()
        self.currentTimer = None
        self.currentTimerTag = None
        for hostName in hostNames:
            stateMachine = HostStateMachine(Host(hostName))
            self.hosts.add(stateMachine)
            self.freePool.put(stateMachine)
        self.tested = Allocations(self.broadcaster, self.hosts, self.freePool, self.osmosisServer)
        self.requirements = dict(node0=dict(imageLabel="echo-foxtrot", imageHint="golf"),
                                 node1=dict(imageLabel="hotel-india", imageHint="juliet"))

    def tearDown(self):
        globallock._lock.release()

    def test_Create(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        self.assertEquals(_allocation, self.tested.byIndex(_allocation.index()))
        self.assertEquals(self.allocationInfo, _allocation.allocationInfo())

    def test_AllocationCreationFails(self):
        origAllocation = allocation.Allocation
        try:
            allocation.Allocation = mock.Mock(side_effect=ValueError("don't wanna"))
            self.assertRaises(ValueError, self.createAllocation, self.requirements, self.allocationInfo)
        finally:
            allocation.Allocation = origAllocation

    def test_NoSuchAllocation(self):
        self.assertRaises(IndexError, self.tested.byIndex, 1)

    def test_OsmosisListLabelsReturnsAnotherLabel(self):
        abused = False

        def anotherLabelMock(cmd):
            if cmd[0:2] == ['osmosis', 'listlabels']:
                return cmd[2] + "_not"
            abused = True
        self.assertRaises(Exception, self.createAllocation, self.requirements, self.allocationInfo,
                          listLabelsMock=anotherLabelMock)
        self.assertFalse(abused)

    def test_CreateCleansUp(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        _allocation.free()

        def createCallback():
            self.createAllocation(self.requirements, self.allocationInfo)

        executeCodeWhileAllocationIsDeadOfHeartbeatTimeout(_allocation, createCallback)
        self.assertNotIn(_allocation, self.tested.all())

    def test_byIndexCleansUp(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        _allocation.free()
        idx = _allocation.index()
        self.assertRaises(IndexError, executeCodeWhileAllocationIsDeadOfHeartbeatTimeout, _allocation,
                          lambda: self.tested.byIndex(idx))

    def test_AllCleansUp(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        _allocation.free()

        def validateNotInAll():
            self.assertNotIn(_allocation, self.tested.all())
        executeCodeWhileAllocationIsDeadOfHeartbeatTimeout(_allocation, validateNotInAll)

    def test_All(self):
        _allocation = self.createAllocation(self.requirements, self.allocationInfo)
        self.assertEquals(self.tested.all(), [_allocation])

    def createAllocation(self, requirements, allocationInfo, listLabelsMock=osmosisListLabelsFoundMock):
        origRun = sh.run
        nrFreeHostsBefore = len(self.freePool.all())
        try:
            sh.run = listLabelsMock
            _allocation = self.tested.create(requirements, self.allocationInfo)
        finally:
            sh.run = origRun
        self.assertEquals(len(self.freePool.all()), nrFreeHostsBefore - len(requirements))
        return _allocation