github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/client/service_create_test.go (about)

     1  package client // import "github.com/docker/docker/client"
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	registrytypes "github.com/docker/docker/api/types/registry"
    15  	"github.com/docker/docker/api/types/swarm"
    16  	"github.com/opencontainers/go-digest"
    17  	"github.com/opencontainers/image-spec/specs-go/v1"
    18  	"gotest.tools/assert"
    19  	is "gotest.tools/assert/cmp"
    20  )
    21  
    22  func TestServiceCreateError(t *testing.T) {
    23  	client := &Client{
    24  		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
    25  	}
    26  	_, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
    27  	if err == nil || err.Error() != "Error response from daemon: Server error" {
    28  		t.Fatalf("expected a Server Error, got %v", err)
    29  	}
    30  }
    31  
    32  func TestServiceCreate(t *testing.T) {
    33  	expectedURL := "/services/create"
    34  	client := &Client{
    35  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
    36  			if !strings.HasPrefix(req.URL.Path, expectedURL) {
    37  				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
    38  			}
    39  			if req.Method != "POST" {
    40  				return nil, fmt.Errorf("expected POST method, got %s", req.Method)
    41  			}
    42  			b, err := json.Marshal(types.ServiceCreateResponse{
    43  				ID: "service_id",
    44  			})
    45  			if err != nil {
    46  				return nil, err
    47  			}
    48  			return &http.Response{
    49  				StatusCode: http.StatusOK,
    50  				Body:       ioutil.NopCloser(bytes.NewReader(b)),
    51  			}, nil
    52  		}),
    53  	}
    54  
    55  	r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{})
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	if r.ID != "service_id" {
    60  		t.Fatalf("expected `service_id`, got %s", r.ID)
    61  	}
    62  }
    63  
    64  func TestServiceCreateCompatiblePlatforms(t *testing.T) {
    65  	client := &Client{
    66  		version: "1.30",
    67  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
    68  			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
    69  				var serviceSpec swarm.ServiceSpec
    70  
    71  				// check if the /distribution endpoint returned correct output
    72  				err := json.NewDecoder(req.Body).Decode(&serviceSpec)
    73  				if err != nil {
    74  					return nil, err
    75  				}
    76  
    77  				assert.Check(t, is.Equal("foobar:1.0@sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", serviceSpec.TaskTemplate.ContainerSpec.Image))
    78  				assert.Check(t, is.Len(serviceSpec.TaskTemplate.Placement.Platforms, 1))
    79  
    80  				p := serviceSpec.TaskTemplate.Placement.Platforms[0]
    81  				b, err := json.Marshal(types.ServiceCreateResponse{
    82  					ID: "service_" + p.OS + "_" + p.Architecture,
    83  				})
    84  				if err != nil {
    85  					return nil, err
    86  				}
    87  				return &http.Response{
    88  					StatusCode: http.StatusOK,
    89  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
    90  				}, nil
    91  			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
    92  				b, err := json.Marshal(registrytypes.DistributionInspect{
    93  					Descriptor: v1.Descriptor{
    94  						Digest: "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96",
    95  					},
    96  					Platforms: []v1.Platform{
    97  						{
    98  							Architecture: "amd64",
    99  							OS:           "linux",
   100  						},
   101  					},
   102  				})
   103  				if err != nil {
   104  					return nil, err
   105  				}
   106  				return &http.Response{
   107  					StatusCode: http.StatusOK,
   108  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
   109  				}, nil
   110  			} else {
   111  				return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
   112  			}
   113  		}),
   114  	}
   115  
   116  	spec := swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: "foobar:1.0"}}}
   117  
   118  	r, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{QueryRegistry: true})
   119  	assert.Check(t, err)
   120  	assert.Check(t, is.Equal("service_linux_amd64", r.ID))
   121  }
   122  
   123  func TestServiceCreateDigestPinning(t *testing.T) {
   124  	dgst := "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96"
   125  	dgstAlt := "sha256:37ffbf3f7497c07584dc9637ffbf3f7497c0758c0537ffbf3f7497c0c88e2bb7"
   126  	serviceCreateImage := ""
   127  	pinByDigestTests := []struct {
   128  		img      string // input image provided by the user
   129  		expected string // expected image after digest pinning
   130  	}{
   131  		// default registry returns familiar string
   132  		{"docker.io/library/alpine", "alpine:latest@" + dgst},
   133  		// provided tag is preserved and digest added
   134  		{"alpine:edge", "alpine:edge@" + dgst},
   135  		// image with provided alternative digest remains unchanged
   136  		{"alpine@" + dgstAlt, "alpine@" + dgstAlt},
   137  		// image with provided tag and alternative digest remains unchanged
   138  		{"alpine:edge@" + dgstAlt, "alpine:edge@" + dgstAlt},
   139  		// image on alternative registry does not result in familiar string
   140  		{"alternate.registry/library/alpine", "alternate.registry/library/alpine:latest@" + dgst},
   141  		// unresolvable image does not get a digest
   142  		{"cannotresolve", "cannotresolve:latest"},
   143  	}
   144  
   145  	client := &Client{
   146  		version: "1.30",
   147  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
   148  			if strings.HasPrefix(req.URL.Path, "/v1.30/services/create") {
   149  				// reset and set image received by the service create endpoint
   150  				serviceCreateImage = ""
   151  				var service swarm.ServiceSpec
   152  				if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
   153  					return nil, fmt.Errorf("could not parse service create request")
   154  				}
   155  				serviceCreateImage = service.TaskTemplate.ContainerSpec.Image
   156  
   157  				b, err := json.Marshal(types.ServiceCreateResponse{
   158  					ID: "service_id",
   159  				})
   160  				if err != nil {
   161  					return nil, err
   162  				}
   163  				return &http.Response{
   164  					StatusCode: http.StatusOK,
   165  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
   166  				}, nil
   167  			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/cannotresolve") {
   168  				// unresolvable image
   169  				return nil, fmt.Errorf("cannot resolve image")
   170  			} else if strings.HasPrefix(req.URL.Path, "/v1.30/distribution/") {
   171  				// resolvable images
   172  				b, err := json.Marshal(registrytypes.DistributionInspect{
   173  					Descriptor: v1.Descriptor{
   174  						Digest: digest.Digest(dgst),
   175  					},
   176  				})
   177  				if err != nil {
   178  					return nil, err
   179  				}
   180  				return &http.Response{
   181  					StatusCode: http.StatusOK,
   182  					Body:       ioutil.NopCloser(bytes.NewReader(b)),
   183  				}, nil
   184  			}
   185  			return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
   186  		}),
   187  	}
   188  
   189  	// run pin by digest tests
   190  	for _, p := range pinByDigestTests {
   191  		r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{
   192  			TaskTemplate: swarm.TaskSpec{
   193  				ContainerSpec: &swarm.ContainerSpec{
   194  					Image: p.img,
   195  				},
   196  			},
   197  		}, types.ServiceCreateOptions{QueryRegistry: true})
   198  
   199  		if err != nil {
   200  			t.Fatal(err)
   201  		}
   202  
   203  		if r.ID != "service_id" {
   204  			t.Fatalf("expected `service_id`, got %s", r.ID)
   205  		}
   206  
   207  		if p.expected != serviceCreateImage {
   208  			t.Fatalf("expected image %s, got %s", p.expected, serviceCreateImage)
   209  		}
   210  	}
   211  }