api: Allow ContainerCreate to take several EndpointsConfig for >= 1.44
The API endpoint `/containers/create` accepts several EndpointsConfig since v1.22 but the daemon would error out in such case. This check is moved from the daemon to the api and is now applied only for API < 1.44, effectively allowing the daemon to create containers connected to several networks. Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
parent
20f9635469
commit
bbcd662532
9 changed files with 123 additions and 42 deletions
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
|
@ -607,6 +608,17 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
|||
}
|
||||
}
|
||||
|
||||
if versions.LessThan(version, "1.44") {
|
||||
// Creating a container connected to several networks is not supported until v1.44.
|
||||
if networkingConfig != nil && len(networkingConfig.EndpointsConfig) > 1 {
|
||||
l := make([]string, 0, len(networkingConfig.EndpointsConfig))
|
||||
for k := range networkingConfig.EndpointsConfig {
|
||||
l = append(l, k)
|
||||
}
|
||||
return errdefs.InvalidParameter(errors.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", ")))
|
||||
}
|
||||
}
|
||||
|
||||
ccr, err := s.backend.ContainerCreate(ctx, types.ContainerCreateConfig{
|
||||
Name: name,
|
||||
Config: config,
|
||||
|
|
|
@ -1363,16 +1363,16 @@ definitions:
|
|||
EndpointsConfig:
|
||||
description: |
|
||||
A mapping of network name to endpoint configuration for that network.
|
||||
The endpoint configuration can be left empty to connect to that
|
||||
network with no particular endpoint configuration.
|
||||
type: "object"
|
||||
additionalProperties:
|
||||
$ref: "#/definitions/EndpointSettings"
|
||||
example:
|
||||
# putting an example here, instead of using the example values from
|
||||
# /definitions/EndpointSettings, because containers/create currently
|
||||
# does not support attaching to multiple networks, so the example request
|
||||
# would be confusing if it showed that multiple networks can be contained
|
||||
# in the EndpointsConfig.
|
||||
# TODO remove once we support multiple networks on container create (see https://github.com/moby/moby/blob/07e6b843594e061f82baa5fa23c2ff7d536c2a05/daemon/create.go#L323)
|
||||
# /definitions/EndpointSettings, because EndpointSettings contains
|
||||
# operational data returned when inspecting a container that we don't
|
||||
# accept here.
|
||||
EndpointsConfig:
|
||||
isolated_nw:
|
||||
IPAMConfig:
|
||||
|
@ -1387,6 +1387,7 @@ definitions:
|
|||
Aliases:
|
||||
- "server_x"
|
||||
- "server_y"
|
||||
database_nw: {}
|
||||
|
||||
NetworkSettings:
|
||||
description: "NetworkSettings exposes the network settings in the API"
|
||||
|
@ -6439,6 +6440,7 @@ paths:
|
|||
Aliases:
|
||||
- "server_x"
|
||||
- "server_y"
|
||||
database_nw: {}
|
||||
|
||||
required: true
|
||||
responses:
|
||||
|
|
|
@ -321,19 +321,11 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *i
|
|||
return nil
|
||||
}
|
||||
|
||||
// Checks if the client set configurations for more than one network while creating a container
|
||||
// Also checks if the IPAMConfig is valid
|
||||
// verifyNetworkingConfig validates if the nwConfig is valid.
|
||||
func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
|
||||
if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 {
|
||||
if nwConfig == nil {
|
||||
return nil
|
||||
}
|
||||
if len(nwConfig.EndpointsConfig) > 1 {
|
||||
l := make([]string, 0, len(nwConfig.EndpointsConfig))
|
||||
for k := range nwConfig.EndpointsConfig {
|
||||
l = append(l, k)
|
||||
}
|
||||
return fmt.Errorf("container cannot be connected to network endpoints: %s", strings.Join(l, ", "))
|
||||
}
|
||||
|
||||
for k, v := range nwConfig.EndpointsConfig {
|
||||
if v == nil {
|
||||
|
|
|
@ -45,6 +45,8 @@ keywords: "API, Docker, rcli, REST, documentation"
|
|||
such, the `CheckDuplicate` field is now deprecated. Note that this change is
|
||||
_unversioned_ and applied to all API versions on daemon that support version
|
||||
1.44.
|
||||
* `POST /containers/create` now accepts multiple `EndpointSettings` in
|
||||
`NetworkingConfig.EndpointSettings`.
|
||||
|
||||
## v1.43 API changes
|
||||
|
||||
|
|
|
@ -556,33 +556,6 @@ func (s *DockerAPISuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
|
|||
assert.ErrorContains(c, err, "no command specified")
|
||||
}
|
||||
|
||||
func (s *DockerAPISuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T) {
|
||||
// Container creation must fail if client specified configurations for more than one network
|
||||
config := container.Config{
|
||||
Image: "busybox",
|
||||
}
|
||||
|
||||
networkingConfig := network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
"net1": {},
|
||||
"net2": {},
|
||||
"net3": {},
|
||||
},
|
||||
}
|
||||
|
||||
apiClient, err := client.NewClientWithOpts(client.FromEnv)
|
||||
assert.NilError(c, err)
|
||||
defer apiClient.Close()
|
||||
|
||||
_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &container.HostConfig{}, &networkingConfig, nil, "")
|
||||
msg := err.Error()
|
||||
// network name order in error message is not deterministic
|
||||
assert.Assert(c, strings.Contains(msg, "container cannot be connected to network endpoints"))
|
||||
assert.Assert(c, strings.Contains(msg, "net1"))
|
||||
assert.Assert(c, strings.Contains(msg, "net2"))
|
||||
assert.Assert(c, strings.Contains(msg, "net3"))
|
||||
}
|
||||
|
||||
func (s *DockerAPISuite) TestContainerAPICreateBridgeNetworkMode(c *testing.T) {
|
||||
// Windows does not support bridge
|
||||
testRequires(c, DaemonIsLinux)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
ctr "github.com/docker/docker/integration/internal/container"
|
||||
"github.com/docker/docker/oci"
|
||||
|
@ -585,3 +586,39 @@ func TestCreateInvalidHostConfig(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateWithMultipleEndpointSettings(t *testing.T) {
|
||||
defer setupTest(t)()
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
apiVersion string
|
||||
expectedErr string
|
||||
}{
|
||||
{apiVersion: "1.44"},
|
||||
{apiVersion: "1.43", expectedErr: "Container cannot be connected to network endpoints"},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run("with API v"+tc.apiVersion, func(t *testing.T) {
|
||||
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(tc.apiVersion))
|
||||
assert.NilError(t, err)
|
||||
|
||||
config := container.Config{
|
||||
Image: "busybox",
|
||||
}
|
||||
networkingConfig := network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
"net1": {},
|
||||
"net2": {},
|
||||
"net3": {},
|
||||
},
|
||||
}
|
||||
_, err = apiClient.ContainerCreate(ctx, &config, &container.HostConfig{}, &networkingConfig, nil, "")
|
||||
if tc.expectedErr == "" {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,17 @@ func WithIPv6(networkName, ip string) func(*TestContainerConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
func WithEndpointSettings(nw string, config *network.EndpointSettings) func(*TestContainerConfig) {
|
||||
return func(c *TestContainerConfig) {
|
||||
if c.NetworkingConfig.EndpointsConfig == nil {
|
||||
c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{}
|
||||
}
|
||||
if _, ok := c.NetworkingConfig.EndpointsConfig[nw]; !ok {
|
||||
c.NetworkingConfig.EndpointsConfig[nw] = config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogDriver sets the log driver to use for the container
|
||||
func WithLogDriver(driver string) func(*TestContainerConfig) {
|
||||
return func(c *TestContainerConfig) {
|
||||
|
|
|
@ -33,3 +33,10 @@ func CreateNoError(ctx context.Context, t *testing.T, client client.APIClient, n
|
|||
assert.NilError(t, err)
|
||||
return name
|
||||
}
|
||||
|
||||
func RemoveNoError(ctx context.Context, t *testing.T, apiClient client.APIClient, name string) {
|
||||
t.Helper()
|
||||
|
||||
err := apiClient.NetworkRemove(ctx, name)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
|
45
integration/network/bridge_test.go
Normal file
45
integration/network/bridge_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
networktypes "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
ctr "github.com/docker/docker/integration/internal/container"
|
||||
"github.com/docker/docker/integration/internal/network"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func TestCreateWithMultiNetworks(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
||||
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.44"), "requires API v1.44")
|
||||
|
||||
ctx := setupTest(t)
|
||||
apiClient := testEnv.APIClient()
|
||||
|
||||
network.CreateNoError(ctx, t, apiClient, "testnet1")
|
||||
defer network.RemoveNoError(ctx, t, apiClient, "testnet1")
|
||||
|
||||
network.CreateNoError(ctx, t, apiClient, "testnet2")
|
||||
defer network.RemoveNoError(ctx, t, apiClient, "testnet2")
|
||||
|
||||
attachCtx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancel()
|
||||
res := ctr.RunAttach(attachCtx, t, apiClient,
|
||||
ctr.WithCmd("ip", "-o", "-4", "addr", "show"),
|
||||
ctr.WithNetworkMode("testnet1"),
|
||||
ctr.WithEndpointSettings("testnet1", &networktypes.EndpointSettings{}),
|
||||
ctr.WithEndpointSettings("testnet2", &networktypes.EndpointSettings{}))
|
||||
|
||||
assert.Equal(t, res.ExitCode, 0)
|
||||
assert.Equal(t, res.Stderr.String(), "")
|
||||
|
||||
// Only interfaces with an IPv4 address are printed by iproute2 when flag -4 is specified. Here, we should have two
|
||||
// interfaces for testnet1 and testnet2, plus lo.
|
||||
ifacesWithAddress := strings.Count(res.Stdout.String(), "\n")
|
||||
assert.Equal(t, ifacesWithAddress, 3)
|
||||
}
|
Loading…
Add table
Reference in a new issue