Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit 4fa18b9

Browse files
author
David Chung
committed
tests
Signed-off-by: David Chung <[email protected]>
1 parent 9aec195 commit 4fa18b9

File tree

3 files changed

+137
-30
lines changed

3 files changed

+137
-30
lines changed

cmd/infrakit/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import (
1919

2020
// TODO - deprecate these in favor of the dynamic commands (see above)
2121
//_ "github.com/docker/infrakit/cmd/infrakit/flavor"
22-
//_ "github.com/docker/infrakit/cmd/infrakit/instance"
2322
//_ "github.com/docker/infrakit/cmd/infrakit/group"
23+
//_ "github.com/docker/infrakit/cmd/infrakit/instance"
2424
//_ "github.com/docker/infrakit/cmd/infrakit/resource"
2525

2626
_ "github.com/docker/infrakit/cmd/infrakit/event"

pkg/x/maxlife/maxlife.go

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package maxlife
22

33
import (
4+
"math"
45
"time"
56

67
logutil "github.com/docker/infrakit/pkg/log"
@@ -19,8 +20,7 @@ type Controller struct {
1920
poll time.Duration
2021
maxlife time.Duration
2122
tags map[string]string
22-
23-
stop chan struct{}
23+
stop chan struct{}
2424
}
2525

2626
// NewController creates a controller based on the given plugin and configurations.
@@ -67,46 +67,28 @@ loop:
6767

6868
func (c *Controller) ensureMaxlife(initialCount int) {
6969

70-
// Count is used to track the steady state... we don't want to keep killing instances
71-
// if the counts are steadily decreasing. The idea here is that once we terminate a resource
72-
// another one will be resurrected so we will be back to steady state.
73-
// Of course it's possible that the size of the cluster actually is decreased. So we'd
74-
// wait for a few samples to get to steady state before we terminate another instance.
75-
// Currently we assume damping == 1 or 1 successive samples of delta >= 0 is sufficient to terminate
76-
// another instance.
77-
78-
last := initialCount
7970
tick := time.Tick(c.poll)
71+
8072
loop:
8173
for {
8274

8375
select {
8476

8577
case now := <-tick:
8678

79+
log.Info("TICK")
80+
8781
described, err := c.plugin.DescribeInstances(c.tags, false)
8882
if err != nil {
8983
// Transient error?
9084
log.Warn("error describing instances", "name", c.name, "err", err)
9185
continue
9286
}
9387

94-
// TODO -- we should compute the 2nd derivative wrt time to make sure we
95-
// are truly in a steady state...
96-
97-
current := len(described)
98-
delta := current - last
99-
last = current
100-
101-
if current < 2 {
102-
log.Info("there are less than 2 instances. No actions.", "name", c.name)
103-
continue
104-
}
105-
106-
if delta < 0 {
107-
// Don't do anything if there are fewer instances at this iteration
108-
// than the last. We want to wait until steady state
109-
log.Info("fewer instances in this round. No actions taken", "name", c.name)
88+
// If we are not in a steady state, don't destroy the instances. This is
89+
// important so that we don't take down the whole cluster without restraint.
90+
if len(described) != initialCount {
91+
log.Info("Not steady state yet. No action")
11092
continue
11193
}
11294

@@ -117,6 +99,9 @@ loop:
11799

118100
// check to make sure the age is over the maxlife
119101
if age(oldest, now) > c.maxlife {
102+
103+
log.Info("Destroying", "oldest", oldest, "age", age(oldest, now), "maxlife", c.maxlife)
104+
120105
// terminate it and hope the group controller restores with a new intance
121106
err = c.plugin.Destroy(oldest.ID)
122107
if err != nil {
@@ -135,15 +120,21 @@ loop:
135120
return
136121
}
137122

123+
// age returns the age to the nearest second
138124
func age(instance instance.Description, now time.Time) (age time.Duration) {
139125
link := types.NewLinkFromMap(instance.Tags)
140126
if link.Valid() {
141127
age = now.Sub(link.Created())
128+
age = time.Duration(math.Floor(age.Seconds())) * time.Second
142129
}
143130
return
144131
}
145132

146-
func maxAge(instances []instance.Description, now time.Time) instance.Description {
133+
func maxAge(instances []instance.Description, now time.Time) (result instance.Description) {
134+
if len(instances) == 0 || instances == nil {
135+
return
136+
}
137+
147138
// check to see if the tags of the instances have links. Links have a creation date and
148139
// we can use it to compute the age
149140
var max time.Duration
@@ -155,5 +146,6 @@ func maxAge(instances []instance.Description, now time.Time) instance.Descriptio
155146
found = i
156147
}
157148
}
158-
return instances[found]
149+
result = instances[found]
150+
return
159151
}

pkg/x/maxlife/maxlife_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package maxlife
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/docker/infrakit/pkg/spi/instance"
9+
fake "github.com/docker/infrakit/pkg/testing/instance"
10+
"github.com/docker/infrakit/pkg/types"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestAge(t *testing.T) {
15+
16+
link := types.NewLink()
17+
created := link.Created()
18+
19+
instance := instance.Description{
20+
ID: instance.ID("test"),
21+
Tags: link.Map(),
22+
}
23+
24+
require.Equal(t, 1*time.Hour, age(instance, created.Add(1*time.Hour)))
25+
require.Equal(t, 59*time.Second, age(instance, created.Add(59*time.Second)))
26+
}
27+
28+
func TestMaxAge(t *testing.T) {
29+
30+
instances := []instance.Description{}
31+
32+
for i := 0; i < 3; i++ {
33+
instances = append(instances, instance.Description{
34+
ID: instance.ID(fmt.Sprintf("test%d", i)),
35+
Tags: types.NewLink().Map(),
36+
})
37+
38+
<-time.After(1 * time.Second)
39+
}
40+
41+
require.True(t, age(instances[0], time.Now()) > 1*time.Second)
42+
maxAge := maxAge(instances, time.Now())
43+
require.Equal(t, "test0", string(maxAge.ID))
44+
45+
}
46+
47+
func TestStartStop(t *testing.T) {
48+
49+
poll := 100 * time.Millisecond
50+
maxlife := 1 * time.Second
51+
tags := map[string]string{}
52+
53+
plugin := &fake.Plugin{
54+
DoDescribeInstances: func(tags map[string]string, details bool) ([]instance.Description, error) {
55+
return nil, nil
56+
},
57+
DoDestroy: func(instance instance.ID) error {
58+
return nil
59+
},
60+
}
61+
62+
controller := NewController("test", plugin, poll, maxlife, tags)
63+
controller.Start()
64+
65+
<-time.After(1 * time.Second)
66+
67+
controller.Stop()
68+
}
69+
70+
func TestEnsureMaxlife(t *testing.T) {
71+
72+
poll := 100 * time.Millisecond
73+
maxlife := 1 * time.Second
74+
tags := map[string]string{}
75+
76+
all := map[instance.ID]instance.Description{}
77+
for i := 0; i < 5; i++ {
78+
inst := instance.Description{
79+
ID: instance.ID(fmt.Sprintf("%d", i)),
80+
Tags: types.NewLink().Map(),
81+
}
82+
all[inst.ID] = inst
83+
<-time.After(500 * time.Millisecond)
84+
}
85+
86+
destroy := make(chan instance.ID, 2)
87+
plugin := &fake.Plugin{
88+
DoDescribeInstances: func(tags map[string]string, details bool) ([]instance.Description, error) {
89+
90+
list := []instance.Description{}
91+
for _, inst := range all {
92+
list = append(list, inst)
93+
}
94+
return list, nil
95+
},
96+
DoDestroy: func(instance instance.ID) error {
97+
delete(all, instance)
98+
destroy <- instance
99+
return nil
100+
},
101+
}
102+
103+
controller := NewController("test", plugin, poll, maxlife, tags)
104+
105+
go controller.ensureMaxlife(len(all))
106+
107+
<-time.After(2 * time.Second)
108+
controller.Stop()
109+
110+
// now read what we were destroying
111+
d := <-destroy
112+
113+
require.Equal(t, instance.ID("0"), d)
114+
115+
}

0 commit comments

Comments
 (0)