Skip to content

Commit

Permalink
containerd: add hostname to appropriate files
Browse files Browse the repository at this point in the history
The hostname is the container's handle. This is now added to /etc/hostname.

Both the hostname and the container's IP is appended to /etc/hosts. The
container's IP is only available once the network has been initialized.
Therefore, the original /etc/hosts has to be updated after. The /etc/hosts on
the container is bindmounted to a file on the worker.  We simply update this
file directly.

issue#6811

Co-authored-by: Muntasir Chowdhury <mchowdhury@pivotal.io>
Signed-off-by: Bohan Chen <bochen@pivotal.io>
  • Loading branch information
chenbh and muntac committed May 19, 2021
1 parent 9a96c60 commit b0de224
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 24 deletions.
2 changes: 1 addition & 1 deletion worker/runtime/backend.go
Expand Up @@ -246,7 +246,7 @@ func (b *GardenBackend) startTask(ctx context.Context, cont containerd.Container
return fmt.Errorf("new task: %w", err)
}

err = b.network.Add(ctx, task)
err = b.network.Add(ctx, task, cont.ID())
if err != nil {
return fmt.Errorf("network add: %w", err)
}
Expand Down
33 changes: 29 additions & 4 deletions worker/runtime/cni_network.go
Expand Up @@ -232,12 +232,20 @@ func (n cniNetwork) SetupMounts(handle string) ([]specs.Mount, error) {

etcHosts, err := n.store.Create(
filepath.Join(handle, "/hosts"),
[]byte("127.0.0.1 localhost"),
[]byte("127.0.0.1 localhost\n"),
)
if err != nil {
return nil, fmt.Errorf("creating /etc/hosts: %w", err)
}

etcHostName, err := n.store.Create(
filepath.Join(handle, "/hostname"),
[]byte(handle + "\n"),
)
if err != nil {
return nil, fmt.Errorf("creating /etc/hostname: %w", err)
}

resolvContents, err := n.generateResolvConfContents()
if err != nil {
return nil, fmt.Errorf("generating resolv.conf: %w", err)
Expand All @@ -257,6 +265,11 @@ func (n cniNetwork) SetupMounts(handle string) ([]specs.Mount, error) {
Type: "bind",
Source: etcHosts,
Options: []string{"bind", "rw"},
}, {
Destination: "/etc/hostname",
Type: "bind",
Source: etcHostName,
Options: []string{"bind", "rw"},
}, {
Destination: "/etc/resolv.conf",
Type: "bind",
Expand Down Expand Up @@ -303,19 +316,31 @@ func (n cniNetwork) generateResolvConfContents() ([]byte, error) {
return []byte(contents), err
}

func (n cniNetwork) Add(ctx context.Context, task containerd.Task) error {
func (n cniNetwork) Add(ctx context.Context, task containerd.Task, containerHandle string) error {
if task == nil {
return ErrInvalidInput("nil task")
}

id, netns := netId(task), netNsPath(task)

_, err := n.client.Setup(ctx, id, netns)
result, err := n.client.Setup(ctx, id, netns)

if err != nil {
return fmt.Errorf("cni net setup: %w", err)
}

return nil
// Find container IP
config, found := result.Interfaces["eth0"]
if !found || len(config.IPConfigs) == 0 {
return fmt.Errorf("cni net setup: no eth0 interface found")
}

// Update /etc/hosts on container
// This could not be done earlier because we only have the container IP after the network has been setup
return n.store.Append(
filepath.Join(containerHandle, "/hosts"),
[]byte(config.IPConfigs[0].IP.String()+" "+containerHandle+"\n"),
)
}

func (n cniNetwork) Remove(ctx context.Context, task containerd.Task) error {
Expand Down
67 changes: 57 additions & 10 deletions worker/runtime/cni_network_test.go
Expand Up @@ -3,6 +3,8 @@ package runtime_test
import (
"context"
"errors"
"github.com/containerd/go-cni"
"net"
"strings"

"github.com/concourse/concourse/worker/runtime"
Expand Down Expand Up @@ -69,33 +71,52 @@ func (s *CNINetworkSuite) TestSetupMountsFailToCreateHosts() {
s.Equal("handle/hosts", fname)
}

func (s *CNINetworkSuite) TestSetupMountsFailToCreateResolvConf() {
s.store.CreateReturnsOnCall(1, "", errors.New("create-resolvconf-err"))
func (s *CNINetworkSuite) TestSetupMountsFailToCreateHostname() {
s.store.CreateReturnsOnCall(1, "", errors.New("create-hostname-err"))

_, err := s.network.SetupMounts("handle")
s.EqualError(errors.Unwrap(err), "create-resolvconf-err")
s.EqualError(errors.Unwrap(err), "create-hostname-err")

s.Equal(2, s.store.CreateCallCount())
fname, _ := s.store.CreateArgsForCall(1)

s.Equal("handle/hostname", fname)
}

func (s *CNINetworkSuite) TestSetupMountsFailToCreateResolvConf() {
s.store.CreateReturnsOnCall(2, "", errors.New("create-resolvconf-err"))

_, err := s.network.SetupMounts("handle")
s.EqualError(errors.Unwrap(err), "create-resolvconf-err")

s.Equal(3, s.store.CreateCallCount())
fname, _ := s.store.CreateArgsForCall(2)

s.Equal("handle/resolv.conf", fname)
}

func (s *CNINetworkSuite) TestSetupMountsReturnsMountpoints() {
s.store.CreateReturnsOnCall(0, "/tmp/handle/etc/hosts", nil)
s.store.CreateReturnsOnCall(1, "/tmp/handle/etc/resolv.conf", nil)
s.store.CreateReturnsOnCall(1, "/tmp/handle/etc/hostname", nil)
s.store.CreateReturnsOnCall(2, "/tmp/handle/etc/resolv.conf", nil)

mounts, err := s.network.SetupMounts("some-handle")
s.NoError(err)

s.Len(mounts, 2)
s.Len(mounts, 3)
s.Equal(mounts, []specs.Mount{
{
Destination: "/etc/hosts",
Type: "bind",
Source: "/tmp/handle/etc/hosts",
Options: []string{"bind", "rw"},
},
{
Destination: "/etc/hostname",
Type: "bind",
Source: "/tmp/handle/etc/hostname",
Options: []string{"bind", "rw"},
},
{
Destination: "/etc/resolv.conf",
Type: "bind",
Expand All @@ -116,7 +137,7 @@ func (s *CNINetworkSuite) TestSetupMountsCallsStoreWithNameServers() {
_, err = network.SetupMounts("some-handle")
s.NoError(err)

_, resolvConfContents := s.store.CreateArgsForCall(1)
_, resolvConfContents := s.store.CreateArgsForCall(2)
s.Equal(resolvConfContents, []byte("nameserver 6.6.7.7\nnameserver 1.2.3.4\n"))
}

Expand All @@ -135,7 +156,7 @@ func (s *CNINetworkSuite) TestSetupMountsCallsStoreWithoutNameServers() {

contents := strings.Join(actualResolvContents, "\n") + "\n"

_, resolvConfContents := s.store.CreateArgsForCall(1)
_, resolvConfContents := s.store.CreateArgsForCall(2)
s.Equal(resolvConfContents, []byte(contents))
}

Expand Down Expand Up @@ -169,24 +190,50 @@ func (s *CNINetworkSuite) TestSetupRestrictedNetworksCreatesEmptyAdminChain() {
}

func (s *CNINetworkSuite) TestAddNilTask() {
err := s.network.Add(context.Background(), nil)
err := s.network.Add(context.Background(), nil, "container-handle")
s.EqualError(err, "nil task")
}

func (s *CNINetworkSuite) TestAddSetupErrors() {
s.cni.SetupReturns(nil, errors.New("setup-err"))
task := new(libcontainerdfakes.FakeTask)

err := s.network.Add(context.Background(), task)
err := s.network.Add(context.Background(), task, "container-handle")
s.EqualError(errors.Unwrap(err), "setup-err")
}

func (s *CNINetworkSuite) TestAddInterfaceNotFound() {
task := new(libcontainerdfakes.FakeTask)
task.PidReturns(123)
task.IDReturns("id")

result := &cni.Result{
Interfaces: make(map[string]*cni.Config, 0),
}
s.cni.SetupReturns(result, nil)
err := s.network.Add(context.Background(), task, "container-handle")
s.EqualError(err, "cni net setup: no eth0 interface found")
}

func (s *CNINetworkSuite) TestAdd() {
task := new(libcontainerdfakes.FakeTask)
task.PidReturns(123)
task.IDReturns("id")

err := s.network.Add(context.Background(), task)
result := &cni.Result{
Interfaces: make(map[string]*cni.Config, 0),
}
result.Interfaces["eth0"] = &cni.Config{
IPConfigs: []*cni.IPConfig{
{
IP: net.IPv4(10, 8, 0, 1),
},
},
}

s.cni.SetupReturns(result, nil)

err := s.network.Add(context.Background(), task, "container-handle")
s.NoError(err)

s.Equal(1, s.cni.SetupCallCount())
Expand Down
22 changes: 22 additions & 0 deletions worker/runtime/file_store.go
Expand Up @@ -16,6 +16,10 @@ type FileStore interface {
//
Create(name string, content []byte) (absPath string, err error)

// Append appends to a file previously created in the store.
//
Append(name string, content []byte) error

// DeleteFile removes a file previously created in the store.
//
Delete(name string) (err error)
Expand Down Expand Up @@ -50,6 +54,24 @@ func (f fileStore) Create(name string, content []byte) (string, error) {
return absPath, nil
}

func (f fileStore) Append(name string, content []byte) error {
absPath := filepath.Join(f.root, name)

file, err := os.OpenFile(absPath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("open file: %w", err)
}
defer file.Close()

if _, err := file.Write(content); err != nil {
return fmt.Errorf("write file: %w", err)
}

return nil
}



func (f fileStore) Delete(path string) error {
absPath := filepath.Join(f.root, path)

Expand Down
11 changes: 11 additions & 0 deletions worker/runtime/file_store_test.go
Expand Up @@ -49,6 +49,17 @@ func (s *FileStoreSuite) TestCreateFileInDir() {
s.Equal("hey", string(content))
}

func (s *FileStoreSuite) TestAppendFile() {
fpath, err := s.store.Create("dir/name", []byte("hey"))
s.NoError(err)

err = s.store.Append("dir/name", []byte(" there"))
s.NoError(err)
content, err := ioutil.ReadFile(fpath)
s.NoError(err)
s.Equal("hey there", string(content))
}

func (s *FileStoreSuite) TestDeleteFile() {
fpath, err := s.store.Create("dir/name", []byte("hey"))
s.NoError(err)
Expand Down
59 changes: 59 additions & 0 deletions worker/runtime/integration/integration_test.go
Expand Up @@ -291,6 +291,65 @@ func (s *IntegrationSuite) TestContainerNetworkEgressWithRestrictedNetworks() {
s.Contains(buf.String(), "connect: connection refused")
}

func (s *IntegrationSuite) TestContainerNetworkHosts() {
namespace := "test-restricted-networks"
requestTimeout := 3 * time.Second

network, err := runtime.NewCNINetwork()

s.NoError(err)

networkOpt := runtime.WithNetwork(network)
customBackend, err := runtime.NewGardenBackend(
libcontainerd.New(
s.containerdSocket(),
namespace,
requestTimeout,
),
networkOpt,
)
s.NoError(err)

s.NoError(customBackend.Start())

handle := uuid()

container, err := customBackend.Create(garden.ContainerSpec{
Handle: handle,
RootFSPath: "raw://" + s.rootfs,
Privileged: true,
})
s.NoError(err)

defer func() {
s.NoError(customBackend.Destroy(handle))
customBackend.Stop()
}()

buf := new(buffer)
proc, err := container.Run(
garden.ProcessSpec{
Path: "/executable",
Args: []string{
"-cat=/etc/hosts",
},
},
garden.ProcessIO{
Stdout: buf,
Stderr: buf,
},
)
s.NoError(err)

exitCode, err := proc.Wait()
s.NoError(err)

fmt.Println(buf.String())

s.Equal(exitCode, 0)
s.Contains(buf.String(), handle)
}

// TestRunPrivileged tests whether we're able to run a process in a privileged
// container.
//
Expand Down
2 changes: 1 addition & 1 deletion worker/runtime/network.go
Expand Up @@ -21,7 +21,7 @@ type Network interface {

// Add adds a task to the network.
//
Add(ctx context.Context, task containerd.Task) (err error)
Add(ctx context.Context, task containerd.Task, containerHandle string) (err error)

// Removes a task from the network.
//
Expand Down

0 comments on commit b0de224

Please sign in to comment.