Browse Source

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

Update service ps to be an API test
Vincent Demeester 7 years ago
parent
commit
15b8d0420b

+ 20 - 24
integration-cli/daemon/daemon.go

@@ -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) {

+ 1 - 3
integration-cli/docker_api_swarm_service_test.go

@@ -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)

+ 2 - 2
integration-cli/docker_cli_authz_plugin_v2_test.go

@@ -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() {

+ 4 - 4
integration-cli/docker_cli_authz_unix_test.go

@@ -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())
 

+ 0 - 86
integration-cli/docker_cli_swarm_test.go

@@ -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)
 

+ 145 - 0
integration/service/inspect_test.go

@@ -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)
+		}
+	}
+}

+ 37 - 0
integration/service/main_test.go

@@ -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()) }
+}

+ 133 - 0
vendor/github.com/gotestyourself/gotestyourself/poll/poll.go

@@ -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()
+		}
+	}
+}