diff --git a/api/swagger.yaml b/api/swagger.yaml index 38759a3d03..b62ec2685b 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -2530,6 +2530,21 @@ definitions: example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" + DNSNames: + description: | + List of all DNS names an endpoint has on a specific network. This + list is based on the container name, network aliases, container short + ID, and hostname. + + These DNS names are non-fully qualified but can contain several dots. + You can get fully qualified DNS names by appending `.`. + For instance, if container name is `my.ctr` and the network is named + `testnet`, `DNSNames` will contain `my.ctr` and the FQDN will be + `my.ctr.testnet`. + type: array + items: + type: string + example: ["foobar", "server_x", "server_y", "my.ctr"] EndpointIPAMConfig: description: | diff --git a/api/types/network/endpoint.go b/api/types/network/endpoint.go index d7d835f6d5..4b3c06a52b 100644 --- a/api/types/network/endpoint.go +++ b/api/types/network/endpoint.go @@ -13,7 +13,7 @@ type EndpointSettings struct { // Configurations IPAMConfig *EndpointIPAMConfig Links []string - Aliases []string + Aliases []string // Aliases holds the list of extra, user-specified DNS names for this endpoint. MacAddress string // Operational data NetworkID string @@ -25,6 +25,9 @@ type EndpointSettings struct { GlobalIPv6Address string GlobalIPv6PrefixLen int DriverOpts map[string]string + // DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to + // generate PTR records. + DNSNames []string } // Copy makes a deep copy of `EndpointSettings` @@ -43,6 +46,12 @@ func (es *EndpointSettings) Copy() *EndpointSettings { aliases := make([]string, 0, len(es.Aliases)) epCopy.Aliases = append(aliases, es.Aliases...) } + + if len(es.DNSNames) > 0 { + epCopy.DNSNames = make([]string, len(es.DNSNames)) + copy(epCopy.DNSNames, es.DNSNames) + } + return &epCopy } diff --git a/daemon/container.go b/daemon/container.go index d2494481b6..ccf29e27cf 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -122,9 +122,8 @@ func (daemon *Daemon) Register(c *container.Container) error { func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) { var ( - id string - err error - noExplicitName = name == "" + id string + err error ) id, name, err = daemon.generateIDAndName(name) if err != nil { @@ -151,7 +150,7 @@ func (daemon *Daemon) newContainer(name string, operatingSystem string, config * base.Config = config base.HostConfig = &containertypes.HostConfig{} base.ImageID = imgID - base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName} + base.NetworkSettings = &network.Settings{} base.Name = name base.Driver = daemon.imageService.StorageDriver() base.OS = operatingSystem diff --git a/daemon/container_operations.go b/daemon/container_operations.go index 0d1596fe80..ff7056295c 100644 --- a/daemon/container_operations.go +++ b/daemon/container_operations.go @@ -19,6 +19,7 @@ import ( "github.com/docker/docker/daemon/network" "github.com/docker/docker/errdefs" "github.com/docker/docker/internal/multierror" + "github.com/docker/docker/internal/sliceutil" "github.com/docker/docker/libnetwork" "github.com/docker/docker/libnetwork/netlabel" "github.com/docker/docker/libnetwork/options" @@ -650,29 +651,7 @@ func cleanOperationalData(es *network.EndpointSettings) { func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error { if containertypes.NetworkMode(n.Name()).IsUserDefined() { - addShortID := true - shortID := stringid.TruncateID(container.ID) - for _, alias := range endpointConfig.Aliases { - if alias == shortID { - addShortID = false - break - } - } - if addShortID { - endpointConfig.Aliases = append(endpointConfig.Aliases, shortID) - } - if container.Name != container.Config.Hostname { - addHostname := true - for _, alias := range endpointConfig.Aliases { - if alias == container.Config.Hostname { - addHostname = false - break - } - } - if addHostname { - endpointConfig.Aliases = append(endpointConfig.Aliases, container.Config.Hostname) - } - } + endpointConfig.DNSNames = buildEndpointDNSNames(container, endpointConfig.Aliases) } if err := validateEndpointSettings(n, n.Name(), endpointConfig); err != nil { @@ -687,6 +666,29 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *lib return nil } +// buildEndpointDNSNames constructs the list of DNSNames that should be assigned to a given endpoint. The order within +// the returned slice is important as the first entry will be used to generate the PTR records (for IPv4 and v6) +// associated to this endpoint. +func buildEndpointDNSNames(ctr *container.Container, aliases []string) []string { + var dnsNames []string + + if ctr.Name != "" { + dnsNames = append(dnsNames, strings.TrimPrefix(ctr.Name, "/")) + } + + dnsNames = append(dnsNames, aliases...) + + if ctr.ID != "" { + dnsNames = append(dnsNames, stringid.TruncateID(ctr.ID)) + } + + if ctr.Config.Hostname != "" { + dnsNames = append(dnsNames, ctr.Config.Hostname) + } + + return sliceutil.Dedup(dnsNames) +} + func (daemon *Daemon) connectToNetwork(cfg *config.Config, container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) { start := time.Now() if container.HostConfig.NetworkMode.IsContainer() { diff --git a/daemon/container_operations_test.go b/daemon/container_operations_test.go new file mode 100644 index 0000000000..aa86a10a0c --- /dev/null +++ b/daemon/container_operations_test.go @@ -0,0 +1,55 @@ +package daemon + +import ( + "encoding/json" + "testing" + + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + "github.com/docker/docker/container" + "github.com/docker/docker/libnetwork" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestDNSNamesOrder(t *testing.T) { + d := &Daemon{} + ctr := &container.Container{ + ID: "35de8003b19e27f636fc6ecbf4d7072558b872a8544f287fd69ad8182ad59023", + Name: "foobar", + Config: &containertypes.Config{ + Hostname: "baz", + }, + } + nw := buildNetwork(t, map[string]any{ + "id": "1234567890", + "name": "testnet", + "networkType": "bridge", + "enableIPv6": false, + }) + epSettings := &networktypes.EndpointSettings{ + Aliases: []string{"myctr"}, + } + + if err := d.updateNetworkConfig(ctr, nw, epSettings, false); err != nil { + t.Fatal(err) + } + + assert.Check(t, is.DeepEqual(epSettings.DNSNames, []string{"foobar", "myctr", "35de8003b19e", "baz"})) +} + +func buildNetwork(t *testing.T, config map[string]any) *libnetwork.Network { + t.Helper() + + b, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + + nw := &libnetwork.Network{} + if err := nw.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + + return nw +} diff --git a/daemon/inspect.go b/daemon/inspect.go index c017572502..1c9285fd97 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -15,6 +15,8 @@ import ( "github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/network" "github.com/docker/docker/errdefs" + "github.com/docker/docker/internal/sliceutil" + "github.com/docker/docker/pkg/stringid" "github.com/docker/go-connections/nat" ) @@ -27,6 +29,18 @@ func (daemon *Daemon) ContainerInspect(ctx context.Context, name string, size bo return daemon.containerInspectPre120(ctx, name) case versions.Equal(version, "1.20"): return daemon.containerInspect120(name) + case versions.LessThan(version, "1.45"): + ctr, err := daemon.ContainerInspectCurrent(ctx, name, size) + if err != nil { + return nil, err + } + + shortCID := stringid.TruncateID(ctr.ID) + for _, ep := range ctr.NetworkSettings.Networks { + ep.Aliases = sliceutil.Dedup(append(ep.Aliases, shortCID, ctr.Config.Hostname)) + } + + return ctr, nil default: return daemon.ContainerInspectCurrent(ctx, name, size) } diff --git a/daemon/network.go b/daemon/network.go index d3e49e8b3b..c4b0c93c1d 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -793,10 +793,6 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e var genericOptions = make(options.Generic) nwName := n.Name() - defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName() - if c.NetworkSettings.IsAnonymousEndpoint || (nwName == defaultNetName && !serviceDiscoveryOnDefaultNetwork()) { - createOptions = append(createOptions, libnetwork.CreateOptionAnonymous()) - } if epConfig != nil { if ipam := epConfig.IPAMConfig; ipam != nil { @@ -822,9 +818,8 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e createOptions = append(createOptions, libnetwork.CreateOptionIpam(ip, ip6, ipList, nil)) } - for _, alias := range epConfig.Aliases { - createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias)) - } + createOptions = append(createOptions, libnetwork.CreateOptionDNSNames(epConfig.DNSNames)) + for k, v := range epConfig.DriverOpts { createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(options.Generic{k: v})) } diff --git a/daemon/network/settings.go b/daemon/network/settings.go index a39cab5a60..c31b139ae8 100644 --- a/daemon/network/settings.go +++ b/daemon/network/settings.go @@ -24,7 +24,6 @@ type Settings struct { Ports nat.PortMap SecondaryIPAddresses []networktypes.Address SecondaryIPv6Addresses []networktypes.Address - IsAnonymousEndpoint bool HasSwarmEndpoint bool } diff --git a/daemon/rename.go b/daemon/rename.go index d758d899b2..728796d657 100644 --- a/daemon/rename.go +++ b/daemon/rename.go @@ -7,6 +7,7 @@ import ( "github.com/containerd/log" "github.com/docker/docker/api/types/events" dockercontainer "github.com/docker/docker/container" + "github.com/docker/docker/daemon/network" "github.com/docker/docker/errdefs" "github.com/docker/docker/libnetwork" "github.com/pkg/errors" @@ -38,7 +39,6 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) { defer container.Unlock() oldName = container.Name - oldIsAnonymousEndpoint := container.NetworkSettings.IsAnonymousEndpoint if oldName == newName { return errdefs.InvalidParameter(errors.New("Renaming a container with the same name as its current name")) @@ -62,12 +62,10 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) { } container.Name = newName - container.NetworkSettings.IsAnonymousEndpoint = false defer func() { if retErr != nil { container.Name = oldName - container.NetworkSettings.IsAnonymousEndpoint = oldIsAnonymousEndpoint daemon.reserveName(container.ID, oldName) for k, v := range links { daemon.containersReplica.ReserveName(oldName+k, v.ID) @@ -101,7 +99,6 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) { defer func() { if retErr != nil { container.Name = oldName - container.NetworkSettings.IsAnonymousEndpoint = oldIsAnonymousEndpoint if err := container.CheckpointTo(daemon.containersReplica); err != nil { log.G(context.TODO()).WithFields(log.Fields{ "containerID": container.ID, @@ -118,10 +115,57 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) { return err } - err = sb.Rename(strings.TrimPrefix(container.Name, "/")) - if err != nil { + if err := sb.Rename(newName[1:]); err != nil { return err } + defer func() { + if retErr != nil { + if err := sb.Rename(oldName); err != nil { + log.G(context.TODO()).WithFields(log.Fields{ + "sandboxID": sid, + "oldName": oldName, + "newName": newName, + "error": err, + }).Errorf("failed to revert sandbox rename") + } + } + }() + + for nwName, epConfig := range container.NetworkSettings.Networks { + nw, err := daemon.FindNetwork(nwName) + if err != nil { + return err + } + + ep, err := nw.EndpointByID(epConfig.EndpointID) + if err != nil { + return err + } + + oldDNSNames := make([]string, len(epConfig.DNSNames)) + copy(oldDNSNames, epConfig.DNSNames) + + epConfig.DNSNames = buildEndpointDNSNames(container, epConfig.Aliases) + if err := ep.UpdateDNSNames(epConfig.DNSNames); err != nil { + return err + } + + defer func(ep *libnetwork.Endpoint, epConfig *network.EndpointSettings, oldDNSNames []string) { + if retErr == nil { + return + } + + epConfig.DNSNames = oldDNSNames + if err := ep.UpdateDNSNames(epConfig.DNSNames); err != nil { + log.G(context.TODO()).WithFields(log.Fields{ + "sandboxID": sid, + "oldName": oldName, + "newName": newName, + "error": err, + }).Errorf("failed to revert DNSNames update") + } + }(ep, epConfig, oldDNSNames) + } } daemon.LogContainerEventWithAttributes(container, events.ActionRename, attributes) diff --git a/docs/api/version-history.md b/docs/api/version-history.md index 4dcff253f2..4efde2bcb5 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -68,6 +68,13 @@ keywords: "API, Docker, rcli, REST, documentation" * The `Container` and `ContainerConfig` fields in the `GET /images/{name}/json` response are deprecated and will no longer be included in API v1.45. * `GET /info` now includes `status` properties in `Runtimes`. +* A new field named `DNSNames` and containing all non-fully qualified DNS names + a container takes on a specific network has been added to `GET /containers/{name:.*}/json`. +* The `Aliases` field returned in calls to `GET /containers/{name:.*}/json` in v1.44 and older + versions contains the short container ID. This will change in the next API version, v1.45. + Starting with that API version, this specific value will be removed from the `Aliases` field + such that this field will reflect exactly the values originally submitted to the + `POST /containers/create` endpoint. The newly introduced `DNSNames` should now be used instead. ## v1.43 API changes diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py index 44256f422f..12b1ecabb8 100644 --- a/hack/make/test-docker-py +++ b/hack/make/test-docker-py @@ -13,7 +13,8 @@ source hack/make/.integration-test-helpers # --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_attach_no_stream # TODO re-enable test_attach_no_stream after https://github.com/docker/docker-py/issues/2513 is resolved # TODO re-enable test_run_container_reading_socket_ws. It's reported in https://github.com/docker/docker-py/issues/1478, and we're getting that error in our tests. -: "${PY_TEST_OPTIONS:=--junitxml=${DEST}/junit-report.xml --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_attach_no_stream --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_run_container_reading_socket_ws}" +# TODO re-enable test_run_with_networking_config once this issue is fixed: https://github.com/moby/moby/pull/46853#issuecomment-1864679942. +: "${PY_TEST_OPTIONS:=--junitxml=${DEST}/junit-report.xml --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_attach_no_stream --deselect=tests/integration/api_container_test.py::AttachContainerTest::test_run_container_reading_socket_ws --deselect=tests/integration/models_containers_test.py::ContainerCollectionTest::test_run_with_networking_config}" # build --squash is not supported with containerd integration. if [ -n "$TEST_INTEGRATION_USE_SNAPSHOTTER" ]; then diff --git a/integration/service/network_test.go b/integration/service/network_test.go index d1fb6372f0..ba4a18a68c 100644 --- a/integration/service/network_test.go +++ b/integration/service/network_test.go @@ -5,6 +5,7 @@ import ( containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" "github.com/docker/docker/integration/internal/container" net "github.com/docker/docker/integration/internal/network" "github.com/docker/docker/integration/internal/swarm" @@ -13,13 +14,13 @@ import ( "gotest.tools/v3/skip" ) -func TestDockerNetworkConnectAlias(t *testing.T) { +func TestDockerNetworkConnectAliasPreV144(t *testing.T) { skip.If(t, testEnv.DaemonInfo.OSType == "windows") ctx := setupTest(t) d := swarm.NewSwarm(ctx, t, testEnv) defer d.Stop(t) - client := d.NewClientT(t) + client := d.NewClientT(t, client.WithVersion("1.43")) defer client.Close() name := t.Name() + "test-alias" diff --git a/internal/sliceutil/sliceutil.go b/internal/sliceutil/sliceutil.go new file mode 100644 index 0000000000..66d90f2dc9 --- /dev/null +++ b/internal/sliceutil/sliceutil.go @@ -0,0 +1,13 @@ +package sliceutil + +func Dedup[T comparable](slice []T) []T { + keys := make(map[T]struct{}) + out := make([]T, 0, len(slice)) + for _, s := range slice { + if _, ok := keys[s]; !ok { + out = append(out, s) + keys[s] = struct{}{} + } + } + return out +} diff --git a/libnetwork/agent.go b/libnetwork/agent.go index 3ff97c53a0..be3091fd0f 100644 --- a/libnetwork/agent.go +++ b/libnetwork/agent.go @@ -598,7 +598,7 @@ func (ep *Endpoint) deleteDriverInfoFromCluster() error { } func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error { - if len(ep.myAliases) == 0 && ep.isAnonymous() || ep.Iface() == nil || ep.Iface().Address() == nil { + if len(ep.dnsNames) == 0 || ep.Iface() == nil || ep.Iface().Address() == nil { return nil } @@ -628,10 +628,8 @@ func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error { return nil } - name := ep.Name() - if ep.isAnonymous() { - name = ep.MyAliases()[0] - } + dnsNames := ep.getDNSNames() + primaryDNSName, dnsAliases := dnsNames[0], dnsNames[1:] var ingressPorts []*PortConfig if ep.svcID != "" { @@ -640,24 +638,24 @@ func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error { if n.ingress { ingressPorts = ep.ingressPorts } - if err := n.getController().addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), name, ep.virtualIP, ingressPorts, ep.svcAliases, ep.myAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil { + if err := n.getController().addServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), primaryDNSName, ep.virtualIP, ingressPorts, ep.svcAliases, dnsAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil { return err } } else { // This is a container simply attached to an attachable network - if err := n.getController().addContainerNameResolution(n.ID(), ep.ID(), name, ep.myAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil { + if err := n.getController().addContainerNameResolution(n.ID(), ep.ID(), primaryDNSName, dnsAliases, ep.Iface().Address().IP, "addServiceInfoToCluster"); err != nil { return err } } buf, err := proto.Marshal(&EndpointRecord{ - Name: name, + Name: primaryDNSName, ServiceName: ep.svcName, ServiceID: ep.svcID, VirtualIP: ep.virtualIP.String(), IngressPorts: ingressPorts, Aliases: ep.svcAliases, - TaskAliases: ep.myAliases, + TaskAliases: dnsAliases, EndpointIP: ep.Iface().Address().IP.String(), ServiceDisabled: false, }) @@ -676,7 +674,7 @@ func (ep *Endpoint) addServiceInfoToCluster(sb *Sandbox) error { } func (ep *Endpoint) deleteServiceInfoFromCluster(sb *Sandbox, fullRemove bool, method string) error { - if len(ep.myAliases) == 0 && ep.isAnonymous() { + if len(ep.dnsNames) == 0 { return nil } @@ -699,10 +697,8 @@ func (ep *Endpoint) deleteServiceInfoFromCluster(sb *Sandbox, fullRemove bool, m return nil } - name := ep.Name() - if ep.isAnonymous() { - name = ep.MyAliases()[0] - } + dnsNames := ep.getDNSNames() + primaryDNSName, dnsAliases := dnsNames[0], dnsNames[1:] // First update the networkDB then locally if fullRemove { @@ -720,12 +716,12 @@ func (ep *Endpoint) deleteServiceInfoFromCluster(sb *Sandbox, fullRemove bool, m if n.ingress { ingressPorts = ep.ingressPorts } - if err := n.getController().rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), name, ep.virtualIP, ingressPorts, ep.svcAliases, ep.myAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster", true, fullRemove); err != nil { + if err := n.getController().rmServiceBinding(ep.svcName, ep.svcID, n.ID(), ep.ID(), primaryDNSName, ep.virtualIP, ingressPorts, ep.svcAliases, dnsAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster", true, fullRemove); err != nil { return err } } else { // This is a container simply attached to an attachable network - if err := n.getController().delContainerNameResolution(n.ID(), ep.ID(), name, ep.myAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster"); err != nil { + if err := n.getController().delContainerNameResolution(n.ID(), ep.ID(), primaryDNSName, dnsAliases, ep.Iface().Address().IP, "deleteServiceInfoFromCluster"); err != nil { return err } } diff --git a/libnetwork/default_gateway.go b/libnetwork/default_gateway.go index 8e7b9981bd..651fee3f75 100644 --- a/libnetwork/default_gateway.go +++ b/libnetwork/default_gateway.go @@ -47,7 +47,7 @@ func (sb *Sandbox) setupDefaultGW() error { } } - createOptions := []EndpointOption{CreateOptionAnonymous()} + createOptions := []EndpointOption{} var gwName string if len(sb.containerID) <= gwEPlen { diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index 16e92b547b..fa53450868 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/containerd/log" + "github.com/docker/docker/internal/sliceutil" "github.com/docker/docker/libnetwork/datastore" "github.com/docker/docker/libnetwork/ipamapi" "github.com/docker/docker/libnetwork/netlabel" @@ -23,21 +24,22 @@ type EndpointOption func(ep *Endpoint) // Endpoint represents a logical connection between a network and a sandbox. type Endpoint struct { - name string - id string - network *Network - iface *EndpointInterface - joinInfo *endpointJoinInfo - sandboxID string - exposedPorts []types.TransportPort - anonymous bool + name string + id string + network *Network + iface *EndpointInterface + joinInfo *endpointJoinInfo + sandboxID string + exposedPorts []types.TransportPort + // dnsNames holds all the non-fully qualified DNS names associated to this endpoint. Order matters: first entry + // will be used for the PTR records associated to the endpoint's IPv4 and IPv6 addresses. + dnsNames []string disableResolution bool generic map[string]interface{} prefAddress net.IP prefAddressV6 net.IP ipamOptions map[string]string aliases map[string]string - myAliases []string svcID string svcName string virtualIP net.IP @@ -64,9 +66,8 @@ func (ep *Endpoint) MarshalJSON() ([]byte, error) { epMap["generic"] = ep.generic } epMap["sandbox"] = ep.sandboxID - epMap["anonymous"] = ep.anonymous + epMap["dnsNames"] = ep.dnsNames epMap["disableResolution"] = ep.disableResolution - epMap["myAliases"] = ep.myAliases epMap["svcName"] = ep.svcName epMap["svcID"] = ep.svcID epMap["virtualIP"] = ep.virtualIP.String() @@ -156,8 +157,9 @@ func (ep *Endpoint) UnmarshalJSON(b []byte) (err error) { } } + var anonymous bool if v, ok := epMap["anonymous"]; ok { - ep.anonymous = v.(bool) + anonymous = v.(bool) } if v, ok := epMap["disableResolution"]; ok { ep.disableResolution = v.(bool) @@ -192,7 +194,23 @@ func (ep *Endpoint) UnmarshalJSON(b []byte) (err error) { ma, _ := json.Marshal(epMap["myAliases"]) var myAliases []string json.Unmarshal(ma, &myAliases) //nolint:errcheck - ep.myAliases = myAliases + + _, hasDNSNames := epMap["dnsNames"] + dn, _ := json.Marshal(epMap["dnsNames"]) + var dnsNames []string + json.Unmarshal(dn, &dnsNames) + ep.dnsNames = dnsNames + + // TODO(aker): remove this migration code in v27 + if !hasDNSNames { + // The field dnsNames was introduced in v25.0. If we don't have it, the on-disk state was written by an older + // daemon, thus we need to populate dnsNames based off of myAliases and anonymous values. + if !anonymous { + myAliases = append([]string{ep.name}, myAliases...) + } + ep.dnsNames = sliceutil.Dedup(myAliases) + } + return nil } @@ -210,7 +228,6 @@ func (ep *Endpoint) CopyTo(o datastore.KVObject) error { dstEp.sandboxID = ep.sandboxID dstEp.dbIndex = ep.dbIndex dstEp.dbExists = ep.dbExists - dstEp.anonymous = ep.anonymous dstEp.disableResolution = ep.disableResolution dstEp.svcName = ep.svcName dstEp.svcID = ep.svcID @@ -240,8 +257,8 @@ func (ep *Endpoint) CopyTo(o datastore.KVObject) error { dstEp.exposedPorts = make([]types.TransportPort, len(ep.exposedPorts)) copy(dstEp.exposedPorts, ep.exposedPorts) - dstEp.myAliases = make([]string, len(ep.myAliases)) - copy(dstEp.myAliases, ep.myAliases) + dstEp.dnsNames = make([]string, len(ep.dnsNames)) + copy(dstEp.dnsNames, ep.dnsNames) dstEp.generic = options.Generic{} for k, v := range ep.generic { @@ -267,13 +284,6 @@ func (ep *Endpoint) Name() string { return ep.name } -func (ep *Endpoint) MyAliases() []string { - ep.mu.Lock() - defer ep.mu.Unlock() - - return ep.myAliases -} - // Network returns the name of the network to which this endpoint is attached. func (ep *Endpoint) Network() string { if ep.network == nil { @@ -283,10 +293,15 @@ func (ep *Endpoint) Network() string { return ep.network.name } -func (ep *Endpoint) isAnonymous() bool { +// getDNSNames returns a copy of the DNS names associated to this endpoint. The first entry is the one used for PTR +// records. +func (ep *Endpoint) getDNSNames() []string { ep.mu.Lock() defer ep.mu.Unlock() - return ep.anonymous + + dnsNames := make([]string, len(ep.dnsNames)) + copy(dnsNames, ep.dnsNames) + return dnsNames } // isServiceEnabled check if service is enabled on the endpoint @@ -568,71 +583,52 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) { } func (ep *Endpoint) rename(name string) error { - var ( - err error - ok bool - ) + ep.mu.Lock() + ep.name = name + ep.mu.Unlock() - n := ep.getNetwork() - if n == nil { - return fmt.Errorf("network not connected for ep %q", ep.name) + // Update the store with the updated name + if err := ep.getNetwork().getController().updateToStore(ep); err != nil { + return err } - c := n.getController() + return nil +} +func (ep *Endpoint) UpdateDNSNames(dnsNames []string) error { + nw := ep.getNetwork() + c := nw.getController() sb, ok := ep.getSandbox() if !ok { - log.G(context.TODO()).Warnf("rename for %s aborted, sandbox %s is not anymore present", ep.ID(), ep.sandboxID) + log.G(context.TODO()).WithFields(log.Fields{ + "sandboxID": ep.sandboxID, + "endpointID": ep.ID(), + }).Warn("DNSNames update aborted, sandbox is not present anymore") return nil } if c.isAgent() { - if err = ep.deleteServiceInfoFromCluster(sb, true, "rename"); err != nil { - return types.InternalErrorf("Could not delete service state for endpoint %s from cluster on rename: %v", ep.Name(), err) + if err := ep.deleteServiceInfoFromCluster(sb, true, "UpdateDNSNames"); err != nil { + return types.InternalErrorf("could not delete service state for endpoint %s from cluster on UpdateDNSNames: %v", ep.Name(), err) + } + + ep.dnsNames = dnsNames + if err := ep.addServiceInfoToCluster(sb); err != nil { + return types.InternalErrorf("could not add service state for endpoint %s to cluster on UpdateDNSNames: %v", ep.Name(), err) } } else { - n.updateSvcRecord(ep, false) - } + nw.updateSvcRecord(ep, false) - oldName := ep.name - oldAnonymous := ep.anonymous - ep.name = name - ep.anonymous = false - - if c.isAgent() { - if err = ep.addServiceInfoToCluster(sb); err != nil { - return types.InternalErrorf("Could not add service state for endpoint %s to cluster on rename: %v", ep.Name(), err) - } - defer func() { - if err != nil { - if err2 := ep.deleteServiceInfoFromCluster(sb, true, "rename"); err2 != nil { - log.G(context.TODO()).WithField("main error", err).WithError(err2).Debug("Error during cleanup due deleting service info from cluster while cleaning up due to other error") - } - ep.name = oldName - ep.anonymous = oldAnonymous - if err2 := ep.addServiceInfoToCluster(sb); err2 != nil { - log.G(context.TODO()).WithField("main error", err).WithError(err2).Debug("Error during cleanup due adding service to from cluster while cleaning up due to other error") - } - } - }() - } else { - n.updateSvcRecord(ep, true) - defer func() { - if err != nil { - n.updateSvcRecord(ep, false) - ep.name = oldName - ep.anonymous = oldAnonymous - n.updateSvcRecord(ep, true) - } - }() + ep.dnsNames = dnsNames + nw.updateSvcRecord(ep, true) } // Update the store with the updated name - if err = c.updateToStore(ep); err != nil { + if err := c.updateToStore(ep); err != nil { return err } - return err + return nil } func (ep *Endpoint) hasInterface(iName string) bool { @@ -951,11 +947,11 @@ func CreateOptionDNS(dns []string) EndpointOption { } } -// CreateOptionAnonymous function returns an option setter for setting -// this endpoint as anonymous -func CreateOptionAnonymous() EndpointOption { +// CreateOptionDNSNames specifies the list of (non fully qualified) DNS names associated to an endpoint. These will be +// used to populate the embedded DNS server. Order matters: first name will be used to generate PTR records. +func CreateOptionDNSNames(names []string) EndpointOption { return func(ep *Endpoint) { - ep.anonymous = true + ep.dnsNames = names } } @@ -988,13 +984,6 @@ func CreateOptionService(name, id string, vip net.IP, ingressPorts []*PortConfig } } -// CreateOptionMyAlias function returns an option setter for setting endpoint's self alias -func CreateOptionMyAlias(alias string) EndpointOption { - return func(ep *Endpoint) { - ep.myAliases = append(ep.myAliases, alias) - } -} - // CreateOptionLoadBalancer function returns an option setter for denoting the endpoint is a load balancer for a network func CreateOptionLoadBalancer() EndpointOption { return func(ep *Endpoint) { diff --git a/libnetwork/libnetwork_internal_test.go b/libnetwork/libnetwork_internal_test.go index b8259ccb19..ef42dba7bf 100644 --- a/libnetwork/libnetwork_internal_test.go +++ b/libnetwork/libnetwork_internal_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net" + "reflect" "runtime" "testing" "time" @@ -191,7 +192,6 @@ func TestEndpointMarshalling(t *testing.T) { name: "Bau", id: "efghijklmno", sandboxID: "ambarabaciccicocco", - anonymous: true, iface: &EndpointInterface{ mac: []byte{11, 12, 13, 14, 15, 16}, addr: &net.IPNet{ @@ -205,6 +205,7 @@ func TestEndpointMarshalling(t *testing.T) { v6PoolID: "poolv6", llAddrs: lla, }, + dnsNames: []string{"test", "foobar", "baz"}, } b, err := json.Marshal(e) @@ -218,7 +219,7 @@ func TestEndpointMarshalling(t *testing.T) { t.Fatal(err) } - if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !compareEndpointInterface(e.iface, ee.iface) || e.anonymous != ee.anonymous { + if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !reflect.DeepEqual(e.dnsNames, ee.dnsNames) || !compareEndpointInterface(e.iface, ee.iface) { t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v\nOriginal iface: %#v\nDecodediface:\n%#v", e, ee, e.iface, ee.iface) } } diff --git a/libnetwork/network.go b/libnetwork/network.go index 75c5de5cad..a4aaf2f90b 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -175,7 +175,7 @@ func (i *IpamInfo) UnmarshalJSON(data []byte) error { type Network struct { ctrlr *Controller name string - networkType string + networkType string // networkType is the name of the netdriver used by this network id string created time.Time scope string // network data scope @@ -1302,8 +1302,6 @@ func (n *Network) updateSvcRecord(ep *Endpoint, isAdd bool) { } var ipv6 net.IP - epName := ep.Name() - myAliases := ep.MyAliases() if iface.AddressIPv6() != nil { ipv6 = iface.AddressIPv6().IP } @@ -1312,30 +1310,17 @@ func (n *Network) updateSvcRecord(ep *Endpoint, isAdd bool) { if serviceID == "" { serviceID = ep.ID() } + + dnsNames := ep.getDNSNames() if isAdd { - // If anonymous endpoint has an alias use the first alias - // for ip->name mapping. Not having the reverse mapping - // breaks some apps - if ep.isAnonymous() { - if len(myAliases) > 0 { - n.addSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") - } - } else { - n.addSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") - } - for _, alias := range myAliases { - n.addSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord") + for i, dnsName := range dnsNames { + ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated. + n.addSvcRecords(ep.ID(), dnsName, serviceID, iface.Address().IP, ipv6, ipMapUpdate, "updateSvcRecord") } } else { - if ep.isAnonymous() { - if len(myAliases) > 0 { - n.deleteSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") - } - } else { - n.deleteSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") - } - for _, alias := range myAliases { - n.deleteSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord") + for i, dnsName := range dnsNames { + ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated. + n.deleteSvcRecords(ep.ID(), dnsName, serviceID, iface.Address().IP, ipv6, ipMapUpdate, "updateSvcRecord") } } } @@ -1374,6 +1359,7 @@ func delNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID strin }) } +// TODO(aker): remove ipMapUpdate param and add a proper method dedicated to update PTR records. func (n *Network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP, ipMapUpdate bool, method string) { // Do not add service names for ingress network as this is a // routing only network @@ -2176,10 +2162,6 @@ func (n *Network) createLoadBalancerSandbox() (retErr error) { CreateOptionIpam(n.loadBalancerIP, nil, nil, nil), CreateOptionLoadBalancer(), } - if n.hasLoadBalancerEndpoint() && !n.ingress { - // Mark LB endpoints as anonymous so they don't show up in DNS - epOptions = append(epOptions, CreateOptionAnonymous()) - } ep, err := n.createEndpoint(endpointName, epOptions...) if err != nil { return err