github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/service/create_test.go (about)

     1  package service // import "github.com/Prakhar-Agarwal-byte/moby/integration/service"
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/Prakhar-Agarwal-byte/moby/api/types"
    11  	"github.com/Prakhar-Agarwal-byte/moby/api/types/container"
    12  	"github.com/Prakhar-Agarwal-byte/moby/api/types/filters"
    13  	"github.com/Prakhar-Agarwal-byte/moby/api/types/strslice"
    14  	swarmtypes "github.com/Prakhar-Agarwal-byte/moby/api/types/swarm"
    15  	"github.com/Prakhar-Agarwal-byte/moby/api/types/versions"
    16  	"github.com/Prakhar-Agarwal-byte/moby/client"
    17  	"github.com/Prakhar-Agarwal-byte/moby/errdefs"
    18  	"github.com/Prakhar-Agarwal-byte/moby/integration/internal/network"
    19  	"github.com/Prakhar-Agarwal-byte/moby/integration/internal/swarm"
    20  	"github.com/Prakhar-Agarwal-byte/moby/testutil"
    21  	"github.com/Prakhar-Agarwal-byte/moby/testutil/daemon"
    22  	"gotest.tools/v3/assert"
    23  	is "gotest.tools/v3/assert/cmp"
    24  	"gotest.tools/v3/poll"
    25  	"gotest.tools/v3/skip"
    26  )
    27  
    28  func TestServiceCreateInit(t *testing.T) {
    29  	ctx := setupTest(t)
    30  	t.Run("daemonInitDisabled", testServiceCreateInit(ctx, false))
    31  	t.Run("daemonInitEnabled", testServiceCreateInit(ctx, true))
    32  }
    33  
    34  func testServiceCreateInit(ctx context.Context, daemonEnabled bool) func(t *testing.T) {
    35  	return func(t *testing.T) {
    36  		_ = testutil.StartSpan(ctx, t)
    37  		ops := []daemon.Option{}
    38  
    39  		if daemonEnabled {
    40  			ops = append(ops, daemon.WithInit())
    41  		}
    42  		d := swarm.NewSwarm(ctx, t, testEnv, ops...)
    43  		defer d.Stop(t)
    44  		client := d.NewClientT(t)
    45  		defer client.Close()
    46  
    47  		booleanTrue := true
    48  		booleanFalse := false
    49  
    50  		serviceID := swarm.CreateService(ctx, t, d)
    51  		poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, 1), swarm.ServicePoll)
    52  		i := inspectServiceContainer(ctx, t, client, serviceID)
    53  		// HostConfig.Init == nil means that it delegates to daemon configuration
    54  		assert.Check(t, i.HostConfig.Init == nil)
    55  
    56  		serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithInit(&booleanTrue))
    57  		poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, 1), swarm.ServicePoll)
    58  		i = inspectServiceContainer(ctx, t, client, serviceID)
    59  		assert.Check(t, is.Equal(true, *i.HostConfig.Init))
    60  
    61  		serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithInit(&booleanFalse))
    62  		poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, 1), swarm.ServicePoll)
    63  		i = inspectServiceContainer(ctx, t, client, serviceID)
    64  		assert.Check(t, is.Equal(false, *i.HostConfig.Init))
    65  	}
    66  }
    67  
    68  func inspectServiceContainer(ctx context.Context, t *testing.T, client client.APIClient, serviceID string) types.ContainerJSON {
    69  	t.Helper()
    70  	containers, err := client.ContainerList(ctx, container.ListOptions{
    71  		Filters: filters.NewArgs(filters.Arg("label", "com.docker.swarm.service.id="+serviceID)),
    72  	})
    73  	assert.NilError(t, err)
    74  	assert.Check(t, is.Len(containers, 1))
    75  
    76  	i, err := client.ContainerInspect(ctx, containers[0].ID)
    77  	assert.NilError(t, err)
    78  	return i
    79  }
    80  
    81  func TestCreateServiceMultipleTimes(t *testing.T) {
    82  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
    83  	ctx := setupTest(t)
    84  
    85  	d := swarm.NewSwarm(ctx, t, testEnv)
    86  	defer d.Stop(t)
    87  	client := d.NewClientT(t)
    88  	defer client.Close()
    89  
    90  	overlayName := "overlay1_" + t.Name()
    91  	overlayID := network.CreateNoError(ctx, t, client, overlayName,
    92  		network.WithDriver("overlay"),
    93  	)
    94  
    95  	var instances uint64 = 4
    96  
    97  	serviceName := "TestService_" + t.Name()
    98  	serviceSpec := []swarm.ServiceSpecOpt{
    99  		swarm.ServiceWithReplicas(instances),
   100  		swarm.ServiceWithName(serviceName),
   101  		swarm.ServiceWithNetwork(overlayName),
   102  	}
   103  
   104  	serviceID := swarm.CreateService(ctx, t, d, serviceSpec...)
   105  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances), swarm.ServicePoll)
   106  
   107  	_, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   108  	assert.NilError(t, err)
   109  
   110  	err = client.ServiceRemove(ctx, serviceID)
   111  	assert.NilError(t, err)
   112  
   113  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
   114  
   115  	serviceID2 := swarm.CreateService(ctx, t, d, serviceSpec...)
   116  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID2, instances), swarm.ServicePoll)
   117  
   118  	err = client.ServiceRemove(ctx, serviceID2)
   119  	assert.NilError(t, err)
   120  
   121  	// we can't just wait on no tasks for the service, counter-intuitively.
   122  	// Tasks may briefly exist but not show up, if they are are in the process
   123  	// of being deallocated. To avoid this case, we should retry network remove
   124  	// a few times, to give tasks time to be deallcoated
   125  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID2), swarm.ServicePoll)
   126  
   127  	for retry := 0; retry < 5; retry++ {
   128  		err = client.NetworkRemove(ctx, overlayID)
   129  		// TODO(dperny): using strings.Contains for error checking is awful,
   130  		// but so is the fact that swarm functions don't return errdefs errors.
   131  		// I don't have time at this moment to fix the latter, so I guess I'll
   132  		// go with the former.
   133  		//
   134  		// The full error we're looking for is something like this:
   135  		//
   136  		// Error response from daemon: rpc error: code = FailedPrecondition desc = network %v is in use by task %v
   137  		//
   138  		// The safest way to catch this, I think, will be to match on "is in
   139  		// use by", as this is an uninterrupted string that best identifies
   140  		// this error.
   141  		if err == nil || !strings.Contains(err.Error(), "is in use by") {
   142  			// if there is no error, or the error isn't this kind of error,
   143  			// then we'll break the loop body, and either fail the test or
   144  			// continue.
   145  			break
   146  		}
   147  	}
   148  	assert.NilError(t, err)
   149  
   150  	poll.WaitOn(t, network.IsRemoved(ctx, client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
   151  }
   152  
   153  func TestCreateServiceConflict(t *testing.T) {
   154  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   155  	ctx := setupTest(t)
   156  
   157  	d := swarm.NewSwarm(ctx, t, testEnv)
   158  	defer d.Stop(t)
   159  	c := d.NewClientT(t)
   160  	defer c.Close()
   161  
   162  	serviceName := "TestService_" + t.Name()
   163  	serviceSpec := []swarm.ServiceSpecOpt{
   164  		swarm.ServiceWithName(serviceName),
   165  	}
   166  
   167  	swarm.CreateService(ctx, t, d, serviceSpec...)
   168  
   169  	spec := swarm.CreateServiceSpec(t, serviceSpec...)
   170  	_, err := c.ServiceCreate(ctx, spec, types.ServiceCreateOptions{})
   171  	assert.Check(t, errdefs.IsConflict(err))
   172  	assert.ErrorContains(t, err, "service "+serviceName+" already exists")
   173  }
   174  
   175  func TestCreateServiceMaxReplicas(t *testing.T) {
   176  	ctx := setupTest(t)
   177  
   178  	d := swarm.NewSwarm(ctx, t, testEnv)
   179  	defer d.Stop(t)
   180  	client := d.NewClientT(t)
   181  	defer client.Close()
   182  
   183  	var maxReplicas uint64 = 2
   184  	serviceSpec := []swarm.ServiceSpecOpt{
   185  		swarm.ServiceWithReplicas(maxReplicas),
   186  		swarm.ServiceWithMaxReplicas(maxReplicas),
   187  	}
   188  
   189  	serviceID := swarm.CreateService(ctx, t, d, serviceSpec...)
   190  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, maxReplicas), swarm.ServicePoll)
   191  
   192  	_, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   193  	assert.NilError(t, err)
   194  }
   195  
   196  func TestCreateServiceSecretFileMode(t *testing.T) {
   197  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   198  	ctx := setupTest(t)
   199  
   200  	d := swarm.NewSwarm(ctx, t, testEnv)
   201  	defer d.Stop(t)
   202  	client := d.NewClientT(t)
   203  	defer client.Close()
   204  
   205  	secretName := "TestSecret_" + t.Name()
   206  	secretResp, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{
   207  		Annotations: swarmtypes.Annotations{
   208  			Name: secretName,
   209  		},
   210  		Data: []byte("TESTSECRET"),
   211  	})
   212  	assert.NilError(t, err)
   213  
   214  	var instances uint64 = 1
   215  	serviceName := "TestService_" + t.Name()
   216  	serviceID := swarm.CreateService(ctx, t, d,
   217  		swarm.ServiceWithReplicas(instances),
   218  		swarm.ServiceWithName(serviceName),
   219  		swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/secret && sleep inf"}),
   220  		swarm.ServiceWithSecret(&swarmtypes.SecretReference{
   221  			File: &swarmtypes.SecretReferenceFileTarget{
   222  				Name: "/etc/secret",
   223  				UID:  "0",
   224  				GID:  "0",
   225  				Mode: 0o777,
   226  			},
   227  			SecretID:   secretResp.ID,
   228  			SecretName: secretName,
   229  		}),
   230  	)
   231  
   232  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances), swarm.ServicePoll)
   233  
   234  	body, err := client.ServiceLogs(ctx, serviceID, container.LogsOptions{
   235  		Tail:       "1",
   236  		ShowStdout: true,
   237  	})
   238  	assert.NilError(t, err)
   239  	defer body.Close()
   240  
   241  	content, err := io.ReadAll(body)
   242  	assert.NilError(t, err)
   243  	assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
   244  
   245  	err = client.ServiceRemove(ctx, serviceID)
   246  	assert.NilError(t, err)
   247  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll)
   248  
   249  	err = client.SecretRemove(ctx, secretName)
   250  	assert.NilError(t, err)
   251  }
   252  
   253  func TestCreateServiceConfigFileMode(t *testing.T) {
   254  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   255  	ctx := setupTest(t)
   256  
   257  	d := swarm.NewSwarm(ctx, t, testEnv)
   258  	defer d.Stop(t)
   259  	client := d.NewClientT(t)
   260  	defer client.Close()
   261  
   262  	configName := "TestConfig_" + t.Name()
   263  	configResp, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{
   264  		Annotations: swarmtypes.Annotations{
   265  			Name: configName,
   266  		},
   267  		Data: []byte("TESTCONFIG"),
   268  	})
   269  	assert.NilError(t, err)
   270  
   271  	var instances uint64 = 1
   272  	serviceName := "TestService_" + t.Name()
   273  	serviceID := swarm.CreateService(ctx, t, d,
   274  		swarm.ServiceWithName(serviceName),
   275  		swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/config && sleep inf"}),
   276  		swarm.ServiceWithReplicas(instances),
   277  		swarm.ServiceWithConfig(&swarmtypes.ConfigReference{
   278  			File: &swarmtypes.ConfigReferenceFileTarget{
   279  				Name: "/etc/config",
   280  				UID:  "0",
   281  				GID:  "0",
   282  				Mode: 0o777,
   283  			},
   284  			ConfigID:   configResp.ID,
   285  			ConfigName: configName,
   286  		}),
   287  	)
   288  
   289  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances))
   290  
   291  	body, err := client.ServiceLogs(ctx, serviceID, container.LogsOptions{
   292  		Tail:       "1",
   293  		ShowStdout: true,
   294  	})
   295  	assert.NilError(t, err)
   296  	defer body.Close()
   297  
   298  	content, err := io.ReadAll(body)
   299  	assert.NilError(t, err)
   300  	assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
   301  
   302  	err = client.ServiceRemove(ctx, serviceID)
   303  	assert.NilError(t, err)
   304  	poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID))
   305  
   306  	err = client.ConfigRemove(ctx, configName)
   307  	assert.NilError(t, err)
   308  }
   309  
   310  // TestServiceCreateSysctls tests that a service created with sysctl options in
   311  // the ContainerSpec correctly applies those options.
   312  //
   313  // To test this, we're going to create a service with the sysctl option
   314  //
   315  //	{"net.ipv4.ip_nonlocal_bind": "0"}
   316  //
   317  // We'll get the service's tasks to get the container ID, and then we'll
   318  // inspect the container. If the output of the container inspect contains the
   319  // sysctl option with the correct value, we can assume that the sysctl has been
   320  // plumbed correctly.
   321  //
   322  // Next, we'll remove that service and create a new service with that option
   323  // set to 1. This means that no matter what the default is, we can be confident
   324  // that the sysctl option is applying as intended.
   325  //
   326  // Additionally, we'll do service and task inspects to verify that the inspect
   327  // output includes the desired sysctl option.
   328  //
   329  // We're using net.ipv4.ip_nonlocal_bind because it's something that I'm fairly
   330  // confident won't be modified by the container runtime, and won't blow
   331  // anything up in the test environment
   332  func TestCreateServiceSysctls(t *testing.T) {
   333  	skip.If(
   334  		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"),
   335  		"setting service sysctls is unsupported before api v1.40",
   336  	)
   337  
   338  	ctx := setupTest(t)
   339  
   340  	d := swarm.NewSwarm(ctx, t, testEnv)
   341  	defer d.Stop(t)
   342  	client := d.NewClientT(t)
   343  	defer client.Close()
   344  
   345  	// run thie block twice, so that no matter what the default value of
   346  	// net.ipv4.ip_nonlocal_bind is, we can verify that setting the sysctl
   347  	// options works
   348  	for _, expected := range []string{"0", "1"} {
   349  		// store the map we're going to be using everywhere.
   350  		expectedSysctls := map[string]string{"net.ipv4.ip_nonlocal_bind": expected}
   351  
   352  		// Create the service with the sysctl options
   353  		var instances uint64 = 1
   354  		serviceID := swarm.CreateService(ctx, t, d,
   355  			swarm.ServiceWithSysctls(expectedSysctls),
   356  		)
   357  
   358  		// wait for the service to converge to 1 running task as expected
   359  		poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances))
   360  
   361  		// we're going to check 3 things:
   362  		//
   363  		//   1. Does the container, when inspected, have the sysctl option set?
   364  		//   2. Does the task have the sysctl in the spec?
   365  		//   3. Does the service have the sysctl in the spec?
   366  		//
   367  		// if all 3 of these things are true, we know that the sysctl has been
   368  		// plumbed correctly through the engine.
   369  		//
   370  		// We don't actually have to get inside the container and check its
   371  		// logs or anything. If we see the sysctl set on the container inspect,
   372  		// we know that the sysctl is plumbed correctly. everything below that
   373  		// level has been tested elsewhere. (thanks @thaJeztah, because an
   374  		// earlier version of this test had to get container logs and was much
   375  		// more complex)
   376  
   377  		// get all tasks of the service, so we can get the container
   378  		tasks, err := client.TaskList(ctx, types.TaskListOptions{
   379  			Filters: filters.NewArgs(filters.Arg("service", serviceID)),
   380  		})
   381  		assert.NilError(t, err)
   382  		assert.Check(t, is.Equal(len(tasks), 1))
   383  
   384  		// verify that the container has the sysctl option set
   385  		ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
   386  		assert.NilError(t, err)
   387  		assert.DeepEqual(t, ctnr.HostConfig.Sysctls, expectedSysctls)
   388  
   389  		// verify that the task has the sysctl option set in the task object
   390  		assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls)
   391  
   392  		// verify that the service also has the sysctl set in the spec.
   393  		service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   394  		assert.NilError(t, err)
   395  		assert.DeepEqual(t,
   396  			service.Spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls,
   397  		)
   398  	}
   399  }
   400  
   401  // TestServiceCreateCapabilities tests that a service created with capabilities options in
   402  // the ContainerSpec correctly applies those options.
   403  //
   404  // To test this, we're going to create a service with the capabilities option
   405  //
   406  //	[]string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
   407  //
   408  // We'll get the service's tasks to get the container ID, and then we'll
   409  // inspect the container. If the output of the container inspect contains the
   410  // capabilities option with the correct value, we can assume that the capabilities has been
   411  // plumbed correctly.
   412  func TestCreateServiceCapabilities(t *testing.T) {
   413  	skip.If(
   414  		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
   415  		"setting service capabilities is unsupported before api v1.41",
   416  	)
   417  
   418  	ctx := setupTest(t)
   419  
   420  	d := swarm.NewSwarm(ctx, t, testEnv)
   421  	defer d.Stop(t)
   422  	client := d.NewClientT(t)
   423  	defer client.Close()
   424  
   425  	// store the map we're going to be using everywhere.
   426  	capAdd := []string{"CAP_SYS_CHROOT"}
   427  	capDrop := []string{"CAP_NET_RAW"}
   428  
   429  	// Create the service with the capabilities options
   430  	var instances uint64 = 1
   431  	serviceID := swarm.CreateService(ctx, t, d,
   432  		swarm.ServiceWithCapabilities(capAdd, capDrop),
   433  	)
   434  
   435  	// wait for the service to converge to 1 running task as expected
   436  	poll.WaitOn(t, swarm.RunningTasksCount(ctx, client, serviceID, instances))
   437  
   438  	// we're going to check 3 things:
   439  	//
   440  	//   1. Does the container, when inspected, have the capabilities option set?
   441  	//   2. Does the task have the capabilities in the spec?
   442  	//   3. Does the service have the capabilities in the spec?
   443  	//
   444  	// if all 3 of these things are true, we know that the capabilities has been
   445  	// plumbed correctly through the engine.
   446  	//
   447  	// We don't actually have to get inside the container and check its
   448  	// logs or anything. If we see the capabilities set on the container inspect,
   449  	// we know that the capabilities is plumbed correctly. everything below that
   450  	// level has been tested elsewhere.
   451  
   452  	// get all tasks of the service, so we can get the container
   453  	tasks, err := client.TaskList(ctx, types.TaskListOptions{
   454  		Filters: filters.NewArgs(filters.Arg("service", serviceID)),
   455  	})
   456  	assert.NilError(t, err)
   457  	assert.Check(t, is.Equal(len(tasks), 1))
   458  
   459  	// verify that the container has the capabilities option set
   460  	ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
   461  	assert.NilError(t, err)
   462  	assert.DeepEqual(t, ctnr.HostConfig.CapAdd, strslice.StrSlice(capAdd))
   463  	assert.DeepEqual(t, ctnr.HostConfig.CapDrop, strslice.StrSlice(capDrop))
   464  
   465  	// verify that the task has the capabilities option set in the task object
   466  	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityAdd, capAdd)
   467  	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityDrop, capDrop)
   468  
   469  	// verify that the service also has the capabilities set in the spec.
   470  	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
   471  	assert.NilError(t, err)
   472  	assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, capAdd)
   473  	assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, capDrop)
   474  }