Skip to content

Commit

Permalink
Work with instances not in ASG (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcinwyszynski authored Dec 11, 2023
1 parent 119e501 commit 06eb4e0
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 14 deletions.
13 changes: 11 additions & 2 deletions internal/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,21 @@ func (c *Controller) KillInstance(ctx context.Context, instanceID string) (err e
ShouldDecrementDesiredCapacity: aws.Bool(true),
})

if err != nil {
// A special instance of the error is when the instance is not part of
// the autoscaling group. This can happen when the instance successfully
// detached but for some reason the termination request failed.
//
// This will fix one-off errors and machines manually connected to the
// worker pool (as long as they terminate upon request), but if there
// are multiple ASGs connected to the same worker pool, this will be a
// common occurrence and will break the entire autoscaling logic.
if err != nil && !strings.Contains(err.Error(), "is not part of Auto Scaling group") {
err = fmt.Errorf("could not detach instance from autoscaling group: %v", err)
return err
}

// Now that the instance is detached from the ASG, we can terminate it.
// Now that the instance is detached from the ASG (or was never part of
// the ASG), we can terminate it.
_, err = c.EC2.TerminateInstances(ctx, &ec2.TerminateInstancesInput{
InstanceIds: []string{instanceID},
})
Expand Down
37 changes: 25 additions & 12 deletions internal/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,13 +440,11 @@ func TestController(t *testing.T) {
})
})

g.Describe("when the detach call succeeds", func() {
g.Describe("when the detach call does not fail", func() {
var terminateCall *mock.Call
var terminateInput *ec2.TerminateInstancesInput

g.BeforeEach(func() {
detachCall.Return(nil, nil)

terminateInput = nil

terminateCall = mockEC2.On(
Expand All @@ -460,23 +458,38 @@ func TestController(t *testing.T) {
)
})

g.Describe("when the terminate call fails", func() {
g.BeforeEach(func() { terminateCall.Return(nil, errors.New("bacon")) })

g.It("send the correct input", func() {
Expect(terminateInput).NotTo(BeNil())
Expect(terminateInput.InstanceIds).To(ConsistOf(instanceID))
g.Describe("when the instance is not part of the ASG", func() {
g.BeforeEach(func() {
detachCall.Return(nil, errors.New("instance is not part of Auto Scaling group"))
terminateCall.Return(nil, errors.New("bacon"))
})

g.It("should return an error", func() {
Expect(err).To(MatchError("could not terminate detached instance: bacon"))
})
})

g.Describe("when the terminate call succeeds", func() {
g.BeforeEach(func() { terminateCall.Return(nil, nil) })
g.Describe("when the detach call succeeds", func() {
g.BeforeEach(func() { detachCall.Return(nil, nil) })

g.Describe("when the terminate call fails", func() {
g.BeforeEach(func() { terminateCall.Return(nil, errors.New("bacon")) })

g.It("send the correct input", func() {
Expect(terminateInput).NotTo(BeNil())
Expect(terminateInput.InstanceIds).To(ConsistOf(instanceID))
})

g.It("succeeds", func() { Expect(err).NotTo(HaveOccurred()) })
g.It("should return an error", func() {
Expect(err).To(MatchError("could not terminate detached instance: bacon"))
})
})

g.Describe("when the terminate call succeeds", func() {
g.BeforeEach(func() { terminateCall.Return(nil, nil) })

g.It("succeeds", func() { Expect(err).NotTo(HaveOccurred()) })
})
})
})
})
Expand Down

0 comments on commit 06eb4e0

Please sign in to comment.