@@ -1573,3 +1573,238 @@ func TestAllocRunner_PersistState_Destroyed(t *testing.T) {
1573
1573
require .NoError (t , err )
1574
1574
require .Nil (t , ts )
1575
1575
}
1576
+
1577
+ func TestAllocRunner_Reconnect (t * testing.T ) {
1578
+ t .Parallel ()
1579
+
1580
+ type tcase struct {
1581
+ clientStatus string
1582
+ taskState string
1583
+ taskEvent * structs.TaskEvent
1584
+ }
1585
+ tcases := []tcase {
1586
+ {
1587
+ structs .AllocClientStatusRunning ,
1588
+ structs .TaskStateRunning ,
1589
+ structs .NewTaskEvent (structs .TaskStarted ),
1590
+ },
1591
+ {
1592
+ structs .AllocClientStatusComplete ,
1593
+ structs .TaskStateDead ,
1594
+ structs .NewTaskEvent (structs .TaskTerminated ),
1595
+ },
1596
+ {
1597
+ structs .AllocClientStatusFailed ,
1598
+ structs .TaskStateDead ,
1599
+ structs .NewTaskEvent (structs .TaskDriverFailure ).SetFailsTask (),
1600
+ },
1601
+ {
1602
+ structs .AllocClientStatusPending ,
1603
+ structs .TaskStatePending ,
1604
+ structs .NewTaskEvent (structs .TaskReceived ),
1605
+ },
1606
+ }
1607
+
1608
+ for _ , tc := range tcases {
1609
+ t .Run (tc .clientStatus , func (t * testing.T ) {
1610
+ // create a running alloc
1611
+ alloc := mock .BatchAlloc ()
1612
+ alloc .AllocModifyIndex = 10
1613
+ alloc .ModifyIndex = 10
1614
+ alloc .ModifyTime = time .Now ().UnixNano ()
1615
+
1616
+ // Ensure task takes some time
1617
+ task := alloc .Job .TaskGroups [0 ].Tasks [0 ]
1618
+ task .Driver = "mock_driver"
1619
+ task .Config ["run_for" ] = "30s"
1620
+
1621
+ original := alloc .Copy ()
1622
+
1623
+ conf , cleanup := testAllocRunnerConfig (t , alloc )
1624
+ defer cleanup ()
1625
+
1626
+ ar , err := NewAllocRunner (conf )
1627
+ require .NoError (t , err )
1628
+ defer destroy (ar )
1629
+
1630
+ go ar .Run ()
1631
+
1632
+ for _ , taskRunner := range ar .tasks {
1633
+ taskRunner .UpdateState (tc .taskState , tc .taskEvent )
1634
+ }
1635
+
1636
+ update := ar .Alloc ().Copy ()
1637
+
1638
+ update .ClientStatus = structs .AllocClientStatusUnknown
1639
+ update .AllocModifyIndex = original .AllocModifyIndex + 10
1640
+ update .ModifyIndex = original .ModifyIndex + 10
1641
+ update .ModifyTime = original .ModifyTime + 10
1642
+
1643
+ err = ar .Reconnect (update )
1644
+ require .NoError (t , err )
1645
+
1646
+ require .Equal (t , tc .clientStatus , ar .AllocState ().ClientStatus )
1647
+
1648
+ // Make sure the runner's alloc indexes match the update.
1649
+ require .Equal (t , update .AllocModifyIndex , ar .Alloc ().AllocModifyIndex )
1650
+ require .Equal (t , update .ModifyIndex , ar .Alloc ().ModifyIndex )
1651
+ require .Equal (t , update .ModifyTime , ar .Alloc ().ModifyTime )
1652
+
1653
+ found := false
1654
+
1655
+ updater := conf .StateUpdater .(* MockStateUpdater )
1656
+ var last * structs.Allocation
1657
+ testutil .WaitForResult (func () (bool , error ) {
1658
+ last = updater .Last ()
1659
+ if last == nil {
1660
+ return false , errors .New ("last update nil" )
1661
+ }
1662
+
1663
+ states := last .TaskStates
1664
+ for _ , s := range states {
1665
+ for _ , e := range s .Events {
1666
+ if e .Type == structs .TaskClientReconnected {
1667
+ found = true
1668
+ return true , nil
1669
+ }
1670
+ }
1671
+ }
1672
+
1673
+ return false , errors .New ("no reconnect event found" )
1674
+ }, func (err error ) {
1675
+ require .NoError (t , err )
1676
+ })
1677
+
1678
+ require .True (t , found , "no reconnect event found" )
1679
+ })
1680
+ }
1681
+ }
1682
+
1683
+ // TestAllocRunner_Lifecycle_Shutdown_Order asserts that a service job with 3
1684
+ // lifecycle hooks (1 sidecar, 1 ephemeral, 1 poststop) starts all 4 tasks, and shuts down
1685
+ // the sidecar after main, but before poststop.
1686
+ func TestAllocRunner_Lifecycle_Shutdown_Order (t * testing.T ) {
1687
+ alloc := mock .LifecycleAllocWithPoststopDeploy ()
1688
+
1689
+ alloc .Job .Type = structs .JobTypeService
1690
+
1691
+ mainTask := alloc .Job .TaskGroups [0 ].Tasks [0 ]
1692
+ mainTask .Config ["run_for" ] = "100s"
1693
+
1694
+ sidecarTask := alloc .Job .TaskGroups [0 ].Tasks [1 ]
1695
+ sidecarTask .Lifecycle .Hook = structs .TaskLifecycleHookPoststart
1696
+ sidecarTask .Config ["run_for" ] = "100s"
1697
+
1698
+ poststopTask := alloc .Job .TaskGroups [0 ].Tasks [2 ]
1699
+ ephemeralTask := alloc .Job .TaskGroups [0 ].Tasks [3 ]
1700
+
1701
+ alloc .Job .TaskGroups [0 ].Tasks = []* structs.Task {mainTask , ephemeralTask , sidecarTask , poststopTask }
1702
+
1703
+ conf , cleanup := testAllocRunnerConfig (t , alloc )
1704
+ defer cleanup ()
1705
+ ar , err := NewAllocRunner (conf )
1706
+ require .NoError (t , err )
1707
+ defer destroy (ar )
1708
+ go ar .Run ()
1709
+
1710
+ upd := conf .StateUpdater .(* MockStateUpdater )
1711
+
1712
+ // Wait for main and sidecar tasks to be running, and that the
1713
+ // ephemeral task ran and exited.
1714
+ testutil .WaitForResult (func () (bool , error ) {
1715
+ last := upd .Last ()
1716
+ if last == nil {
1717
+ return false , fmt .Errorf ("No updates" )
1718
+ }
1719
+
1720
+ if last .ClientStatus != structs .AllocClientStatusRunning {
1721
+ return false , fmt .Errorf ("expected alloc to be running not %s" , last .ClientStatus )
1722
+ }
1723
+
1724
+ if s := last .TaskStates [mainTask .Name ].State ; s != structs .TaskStateRunning {
1725
+ return false , fmt .Errorf ("expected main task to be running not %s" , s )
1726
+ }
1727
+
1728
+ if s := last .TaskStates [sidecarTask .Name ].State ; s != structs .TaskStateRunning {
1729
+ return false , fmt .Errorf ("expected sidecar task to be running not %s" , s )
1730
+ }
1731
+
1732
+ if s := last .TaskStates [ephemeralTask .Name ].State ; s != structs .TaskStateDead {
1733
+ return false , fmt .Errorf ("expected ephemeral task to be dead not %s" , s )
1734
+ }
1735
+
1736
+ if last .TaskStates [ephemeralTask .Name ].Failed {
1737
+ return false , fmt .Errorf ("expected ephemeral task to be successful not failed" )
1738
+ }
1739
+
1740
+ return true , nil
1741
+ }, func (err error ) {
1742
+ t .Fatalf ("error waiting for initial state:\n %v" , err )
1743
+ })
1744
+
1745
+ // Tell the alloc to stop
1746
+ stopAlloc := alloc .Copy ()
1747
+ stopAlloc .DesiredStatus = structs .AllocDesiredStatusStop
1748
+ ar .Update (stopAlloc )
1749
+
1750
+ // Wait for tasks to stop.
1751
+ testutil .WaitForResult (func () (bool , error ) {
1752
+ last := upd .Last ()
1753
+
1754
+ if s := last .TaskStates [ephemeralTask .Name ].State ; s != structs .TaskStateDead {
1755
+ return false , fmt .Errorf ("expected ephemeral task to be dead not %s" , s )
1756
+ }
1757
+
1758
+ if last .TaskStates [ephemeralTask .Name ].Failed {
1759
+ return false , fmt .Errorf ("expected ephemeral task to be successful not failed" )
1760
+ }
1761
+
1762
+ if s := last .TaskStates [mainTask .Name ].State ; s != structs .TaskStateDead {
1763
+ return false , fmt .Errorf ("expected main task to be dead not %s" , s )
1764
+ }
1765
+
1766
+ if last .TaskStates [mainTask .Name ].Failed {
1767
+ return false , fmt .Errorf ("expected main task to be successful not failed" )
1768
+ }
1769
+
1770
+ if s := last .TaskStates [sidecarTask .Name ].State ; s != structs .TaskStateDead {
1771
+ return false , fmt .Errorf ("expected sidecar task to be dead not %s" , s )
1772
+ }
1773
+
1774
+ if last .TaskStates [sidecarTask .Name ].Failed {
1775
+ return false , fmt .Errorf ("expected sidecar task to be successful not failed" )
1776
+ }
1777
+
1778
+ if s := last .TaskStates [poststopTask .Name ].State ; s != structs .TaskStateRunning {
1779
+ return false , fmt .Errorf ("expected poststop task to be running not %s" , s )
1780
+ }
1781
+
1782
+ return true , nil
1783
+ }, func (err error ) {
1784
+ t .Fatalf ("error waiting for kill state:\n %v" , err )
1785
+ })
1786
+
1787
+ last := upd .Last ()
1788
+ require .Less (t , last .TaskStates [ephemeralTask .Name ].FinishedAt , last .TaskStates [mainTask .Name ].FinishedAt )
1789
+ require .Less (t , last .TaskStates [mainTask .Name ].FinishedAt , last .TaskStates [sidecarTask .Name ].FinishedAt )
1790
+
1791
+ // Wait for poststop task to stop.
1792
+ testutil .WaitForResult (func () (bool , error ) {
1793
+ last := upd .Last ()
1794
+
1795
+ if s := last .TaskStates [poststopTask .Name ].State ; s != structs .TaskStateDead {
1796
+ return false , fmt .Errorf ("expected poststop task to be dead not %s" , s )
1797
+ }
1798
+
1799
+ if last .TaskStates [poststopTask .Name ].Failed {
1800
+ return false , fmt .Errorf ("expected poststop task to be successful not failed" )
1801
+ }
1802
+
1803
+ return true , nil
1804
+ }, func (err error ) {
1805
+ t .Fatalf ("error waiting for poststop state:\n %v" , err )
1806
+ })
1807
+
1808
+ last = upd .Last ()
1809
+ require .Less (t , last .TaskStates [sidecarTask .Name ].FinishedAt , last .TaskStates [poststopTask .Name ].FinishedAt )
1810
+ }
0 commit comments