Merge pull request #33684 from dnephin/update-service-ps-tests

Update service ps to be an API test
This commit is contained in:
Vincent Demeester 2017-08-29 09:44:20 +02:00 committed by GitHub
commit 15b8d0420b
8 changed files with 342 additions and 119 deletions

View File

@ -33,6 +33,7 @@ import (
)
type testingT interface {
require.TestingT
logT
Fatalf(string, ...interface{})
}
@ -329,9 +330,7 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
// then save the busybox image from the main daemon and load it into this Daemon instance.
func (d *Daemon) StartWithBusybox(t testingT, arg ...string) {
d.Start(t, arg...)
if err := d.LoadBusybox(); err != nil {
t.Fatalf("Error loading busybox image to current daemon: %s\n%v", d.id, err)
}
d.LoadBusybox(t)
}
// Kill will send a SIGKILL to the daemon
@ -493,27 +492,24 @@ func (d *Daemon) handleUserns() {
}
}
// LoadBusybox will load the stored busybox into a newly started daemon
func (d *Daemon) LoadBusybox() error {
bb := filepath.Join(d.Folder, "busybox.tar")
if _, err := os.Stat(bb); err != nil {
if !os.IsNotExist(err) {
return errors.Errorf("unexpected error on busybox.tar stat: %v", err)
}
// saving busybox image from main daemon
if out, err := exec.Command(d.dockerBinary, "save", "--output", bb, "busybox:latest").CombinedOutput(); err != nil {
imagesOut, _ := exec.Command(d.dockerBinary, "images", "--format", "{{ .Repository }}:{{ .Tag }}").CombinedOutput()
return errors.Errorf("could not save busybox image: %s\n%s", string(out), strings.TrimSpace(string(imagesOut)))
}
}
// loading busybox image to this daemon
if out, err := d.Cmd("load", "--input", bb); err != nil {
return errors.Errorf("could not load busybox image: %s", out)
}
if err := os.Remove(bb); err != nil {
return err
}
return nil
// LoadBusybox image into the daemon
func (d *Daemon) LoadBusybox(t testingT) {
clientHost, err := client.NewEnvClient()
require.NoError(t, err, "failed to create client")
defer clientHost.Close()
ctx := context.Background()
reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"})
require.NoError(t, err, "failed to download busybox")
defer reader.Close()
client, err := d.NewClient()
require.NoError(t, err, "failed to create client")
defer client.Close()
resp, err := client.ImageLoad(ctx, reader, true)
require.NoError(t, err, "failed to load busybox")
defer resp.Body.Close()
}
func (d *Daemon) queryRootDir() (string, error) {

View File

@ -69,9 +69,7 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesCreate(c *check.C) {
c.Assert(err, checker.IsNil)
defer cli.Close()
options := types.ServiceInspectOptions{
InsertDefaults: true,
}
options := types.ServiceInspectOptions{InsertDefaults: true}
// insertDefaults inserts UpdateConfig when service is fetched by ID
resp, _, err := cli.ServiceInspectWithRaw(context.Background(), id, options)

View File

@ -53,7 +53,7 @@ func (s *DockerAuthzV2Suite) TestAuthZPluginAllowNonVolumeRequest(c *check.C) {
// start the daemon with the plugin and load busybox, --net=none build fails otherwise
// because it needs to pull busybox
s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
c.Assert(s.d.LoadBusybox(), check.IsNil)
s.d.LoadBusybox(c)
// defer disabling the plugin
defer func() {
@ -83,7 +83,7 @@ func (s *DockerAuthzV2Suite) TestAuthZPluginDisable(c *check.C) {
// start the daemon with the plugin and load busybox, --net=none build fails otherwise
// because it needs to pull busybox
s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
c.Assert(s.d.LoadBusybox(), check.IsNil)
s.d.LoadBusybox(c)
// defer removing the plugin
defer func() {

View File

@ -209,7 +209,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
s.ctrl.reqRes.Allow = true
s.ctrl.resRes.Allow = true
c.Assert(s.d.LoadBusybox(), check.IsNil)
s.d.LoadBusybox(c)
// Ensure command successful
out, err := s.d.Cmd("run", "-d", "busybox", "top")
@ -322,7 +322,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginAllowEventStream(c *check.C) {
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
s.ctrl.reqRes.Allow = true
s.ctrl.resRes.Allow = true
c.Assert(s.d.LoadBusybox(), check.IsNil)
s.d.LoadBusybox(c)
startTime := strconv.FormatInt(daemonTime(c).Unix(), 10)
// Add another command to to enable event pipelining
@ -418,7 +418,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginEnsureLoadImportWorking(c *check.C) {
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
s.ctrl.reqRes.Allow = true
s.ctrl.resRes.Allow = true
c.Assert(s.d.LoadBusybox(), check.IsNil)
s.d.LoadBusybox(c)
tmp, err := ioutil.TempDir("", "test-authz-load-import")
c.Assert(err, check.IsNil)
@ -445,7 +445,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginHeader(c *check.C) {
s.d.Start(c, "--debug", "--authorization-plugin="+testAuthZPlugin)
s.ctrl.reqRes.Allow = true
s.ctrl.resRes.Allow = true
c.Assert(s.d.LoadBusybox(), check.IsNil)
s.d.LoadBusybox(c)
daemonURL, err := url.Parse(s.d.Sock())

View File

@ -1534,22 +1534,6 @@ func (s *DockerSwarmSuite) TestSwarmManagerAddress(c *check.C) {
c.Assert(out, checker.Contains, expectedOutput)
}
func (s *DockerSwarmSuite) TestSwarmServiceInspectPretty(c *check.C) {
d := s.AddDaemon(c, true, true)
name := "top"
out, err := d.Cmd("service", "create", "--no-resolve-image", "--name", name, "--limit-cpu=0.5", "busybox", "top")
c.Assert(err, checker.IsNil, check.Commentf(out))
expectedOutput := `
Resources:
Limits:
CPU: 0.5`
out, err = d.Cmd("service", "inspect", "--pretty", name)
c.Assert(err, checker.IsNil, check.Commentf(out))
c.Assert(out, checker.Contains, expectedOutput, check.Commentf(out))
}
func (s *DockerSwarmSuite) TestSwarmNetworkIPAMOptions(c *check.C) {
d := s.AddDaemon(c, true, true)
@ -1691,76 +1675,6 @@ func (s *DockerSwarmSuite) TestSwarmNetworkCreateDup(c *check.C) {
}
}
func (s *DockerSwarmSuite) TestSwarmServicePsMultipleServiceIDs(c *check.C) {
d := s.AddDaemon(c, true, true)
name1 := "top1"
out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--name", name1, "--replicas=3", "busybox", "top")
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
id1 := strings.TrimSpace(out)
name2 := "top2"
out, err = d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--name", name2, "--replicas=3", "busybox", "top")
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
id2 := strings.TrimSpace(out)
// make sure task has been deployed.
waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 6)
out, err = d.Cmd("service", "ps", name1)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, name1+".1")
c.Assert(out, checker.Contains, name1+".2")
c.Assert(out, checker.Contains, name1+".3")
c.Assert(out, checker.Not(checker.Contains), name2+".1")
c.Assert(out, checker.Not(checker.Contains), name2+".2")
c.Assert(out, checker.Not(checker.Contains), name2+".3")
out, err = d.Cmd("service", "ps", name1, name2)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, name1+".1")
c.Assert(out, checker.Contains, name1+".2")
c.Assert(out, checker.Contains, name1+".3")
c.Assert(out, checker.Contains, name2+".1")
c.Assert(out, checker.Contains, name2+".2")
c.Assert(out, checker.Contains, name2+".3")
// Name Prefix
out, err = d.Cmd("service", "ps", "to")
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, name1+".1")
c.Assert(out, checker.Contains, name1+".2")
c.Assert(out, checker.Contains, name1+".3")
c.Assert(out, checker.Contains, name2+".1")
c.Assert(out, checker.Contains, name2+".2")
c.Assert(out, checker.Contains, name2+".3")
// Name Prefix (no hit)
out, err = d.Cmd("service", "ps", "noname")
c.Assert(err, checker.NotNil)
c.Assert(out, checker.Contains, "no such services: noname")
out, err = d.Cmd("service", "ps", id1)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, name1+".1")
c.Assert(out, checker.Contains, name1+".2")
c.Assert(out, checker.Contains, name1+".3")
c.Assert(out, checker.Not(checker.Contains), name2+".1")
c.Assert(out, checker.Not(checker.Contains), name2+".2")
c.Assert(out, checker.Not(checker.Contains), name2+".3")
out, err = d.Cmd("service", "ps", id1, id2)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Contains, name1+".1")
c.Assert(out, checker.Contains, name1+".2")
c.Assert(out, checker.Contains, name1+".3")
c.Assert(out, checker.Contains, name2+".1")
c.Assert(out, checker.Contains, name2+".2")
c.Assert(out, checker.Contains, name2+".3")
}
func (s *DockerSwarmSuite) TestSwarmPublishDuplicatePorts(c *check.C) {
d := s.AddDaemon(c, true, true)

View File

@ -0,0 +1,145 @@
package service
import (
"fmt"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/integration-cli/request"
"github.com/gotestyourself/gotestyourself/poll"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestInspect(t *testing.T) {
d := newSwarm(t)
defer d.Stop(t)
client, err := request.NewClientForHost(d.Sock())
require.NoError(t, err)
var before = time.Now()
var instances uint64 = 2
serviceSpec := fullSwarmServiceSpec("test-service-inspect", instances)
ctx := context.Background()
resp, err := client.ServiceCreate(ctx, serviceSpec, types.ServiceCreateOptions{
QueryRegistry: false,
})
require.NoError(t, err)
id := resp.ID
poll.WaitOn(t, serviceContainerCount(client, id, instances))
service, _, err := client.ServiceInspectWithRaw(ctx, id, types.ServiceInspectOptions{})
require.NoError(t, err)
assert.Equal(t, serviceSpec, service.Spec)
assert.Equal(t, uint64(11), service.Meta.Version.Index)
assert.Equal(t, id, service.ID)
assert.WithinDuration(t, before, service.CreatedAt, 30*time.Second)
assert.WithinDuration(t, before, service.UpdatedAt, 30*time.Second)
}
func fullSwarmServiceSpec(name string, replicas uint64) swarm.ServiceSpec {
restartDelay := 100 * time.Millisecond
maxAttempts := uint64(4)
return swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: name,
Labels: map[string]string{
"service-label": "service-label-value",
},
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: "busybox:latest",
Labels: map[string]string{"container-label": "container-value"},
Command: []string{"/bin/top"},
Args: []string{"-u", "root"},
Hostname: "hostname",
Env: []string{"envvar=envvalue"},
Dir: "/work",
User: "root",
StopSignal: "SIGINT",
StopGracePeriod: &restartDelay,
Hosts: []string{"8.8.8.8 google"},
DNSConfig: &swarm.DNSConfig{
Nameservers: []string{"8.8.8.8"},
Search: []string{"somedomain"},
},
},
RestartPolicy: &swarm.RestartPolicy{
Delay: &restartDelay,
Condition: swarm.RestartPolicyConditionOnFailure,
MaxAttempts: &maxAttempts,
},
Runtime: swarm.RuntimeContainer,
},
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{
Replicas: &replicas,
},
},
UpdateConfig: &swarm.UpdateConfig{
Parallelism: 2,
Delay: 200 * time.Second,
FailureAction: swarm.UpdateFailureActionContinue,
Monitor: 2 * time.Second,
MaxFailureRatio: 0.2,
Order: swarm.UpdateOrderStopFirst,
},
RollbackConfig: &swarm.UpdateConfig{
Parallelism: 3,
Delay: 300 * time.Second,
FailureAction: swarm.UpdateFailureActionPause,
Monitor: 3 * time.Second,
MaxFailureRatio: 0.3,
Order: swarm.UpdateOrderStartFirst,
},
}
}
const defaultSwarmPort = 2477
func newSwarm(t *testing.T) *daemon.Swarm {
d := &daemon.Swarm{
Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
Experimental: testEnv.ExperimentalDaemon(),
}),
// TODO: better method of finding an unused port
Port: defaultSwarmPort,
}
// TODO: move to a NewSwarm constructor
d.ListenAddr = fmt.Sprintf("0.0.0.0:%d", d.Port)
// avoid networking conflicts
args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"}
d.StartWithBusybox(t, args...)
require.NoError(t, d.Init(swarm.InitRequest{}))
return d
}
func serviceContainerCount(client client.ServiceAPIClient, id string, count uint64) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
filter.Add("service", id)
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
switch {
case err != nil:
return poll.Error(err)
case len(tasks) == int(count):
return poll.Success()
default:
return poll.Continue("task count at %d waiting for %d", len(tasks), count)
}
}
}

View File

@ -0,0 +1,37 @@
package service
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/integration-cli/environment"
)
var testEnv *environment.Execution
const dockerdBinary = "dockerd"
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// TODO: replace this with `testEnv.Print()` to print the full env
if testEnv.LocalDaemon() {
fmt.Println("INFO: Testing against a local daemon")
} else {
fmt.Println("INFO: Testing against a remote daemon")
}
res := m.Run()
os.Exit(res)
}
func setupTest(t *testing.T) func() {
environment.ProtectImages(t, testEnv)
return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
}

View File

@ -0,0 +1,133 @@
/*Package poll provides tools for testing asynchronous code.
*/
package poll
import (
"fmt"
"time"
)
// TestingT is the subset of testing.T used by WaitOn
type TestingT interface {
LogT
Fatalf(format string, args ...interface{})
}
// LogT is a logging interface that is passed to the WaitOn check function
type LogT interface {
Log(args ...interface{})
Logf(format string, args ...interface{})
}
// Settings are used to configure the behaviour of WaitOn
type Settings struct {
// Timeout is the maximum time to wait for the condition. Defaults to 10s
Timeout time.Duration
// Delay is the time to sleep between checking the condition. Detaults to
// 1ms
Delay time.Duration
}
func defaultConfig() *Settings {
return &Settings{Timeout: 10 * time.Second, Delay: time.Millisecond}
}
// SettingOp is a function which accepts and modifies Settings
type SettingOp func(config *Settings)
// WithDelay sets the delay to wait between polls
func WithDelay(delay time.Duration) SettingOp {
return func(config *Settings) {
config.Delay = delay
}
}
// WithTimeout sets the timeout
func WithTimeout(timeout time.Duration) SettingOp {
return func(config *Settings) {
config.Timeout = timeout
}
}
// Result of a check performed by WaitOn
type Result interface {
// Error indicates that the check failed and polling should stop, and the
// the has failed
Error() error
// Done indicates that polling should stop, and the test should proceed
Done() bool
// Message provides the most recent state when polling has not completed
Message() string
}
type result struct {
done bool
message string
err error
}
func (r result) Done() bool {
return r.done
}
func (r result) Message() string {
return r.message
}
func (r result) Error() error {
return r.err
}
// Continue returns a Result that indicates to WaitOn that it should continue
// polling. The message text will be used as the failure message if the timeout
// is reached.
func Continue(message string, args ...interface{}) Result {
return result{message: fmt.Sprintf(message, args...)}
}
// Success returns a Result where Done() returns true, which indicates to WaitOn
// that it should stop polling and exit without an error.
func Success() Result {
return result{done: true}
}
// Error returns a Result that indicates to WaitOn that it should fail the test
// and stop polling.
func Error(err error) Result {
return result{err: err}
}
// WaitOn a condition or until a timeout. Poll by calling check and exit when
// check returns a done Result. To fail a test and exit polling with an error
// return a error result.
func WaitOn(t TestingT, check func(t LogT) Result, pollOps ...SettingOp) {
config := defaultConfig()
for _, pollOp := range pollOps {
pollOp(config)
}
var lastMessage string
after := time.After(config.Timeout)
chResult := make(chan Result)
for {
go func() {
chResult <- check(t)
}()
select {
case <-after:
if lastMessage == "" {
lastMessage = "first check never completed"
}
t.Fatalf("timeout hit after %s: %s", config.Timeout, lastMessage)
case result := <-chResult:
switch {
case result.Error() != nil:
t.Fatalf("polling check failed: %s", result.Error())
case result.Done():
return
}
time.Sleep(config.Delay)
lastMessage = result.Message()
}
}
}