generate and maintain resolv.conf for sandbox

Signed-off-by: Crazykev <crazykev@zju.edu.cn>
This commit is contained in:
Crazykev 2017-05-24 15:38:42 +08:00
parent d0949687b4
commit 9bf7ffd51a
7 changed files with 99 additions and 12 deletions

View File

@ -18,6 +18,7 @@ package os
import ( import (
"io" "io"
"io/ioutil"
"os" "os"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -33,6 +34,7 @@ type OS interface {
OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
Stat(name string) (os.FileInfo, error) Stat(name string) (os.FileInfo, error)
CopyFile(src, dest string, perm os.FileMode) error CopyFile(src, dest string, perm os.FileMode) error
WriteFile(filename string, data []byte, perm os.FileMode) error
} }
// RealOS is used to dispatch the real system level operations. // RealOS is used to dispatch the real system level operations.
@ -75,3 +77,8 @@ func (RealOS) CopyFile(src, dest string, perm os.FileMode) error {
_, err = io.Copy(out, in) _, err = io.Copy(out, in)
return err return err
} }
// WriteFile will call ioutil.WriteFile to write data into a file.
func (RealOS) WriteFile(filename string, data []byte, perm os.FileMode) error {
return ioutil.WriteFile(filename, data, perm)
}

View File

@ -36,6 +36,7 @@ type FakeOS struct {
OpenFifoFn func(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error) OpenFifoFn func(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error)
StatFn func(string) (os.FileInfo, error) StatFn func(string) (os.FileInfo, error)
CopyFileFn func(string, string, os.FileMode) error CopyFileFn func(string, string, os.FileMode) error
WriteFileFn func(string, []byte, os.FileMode) error
errors map[string]error errors map[string]error
} }
@ -142,3 +143,15 @@ func (f *FakeOS) CopyFile(src, dest string, perm os.FileMode) error {
} }
return nil return nil
} }
// WriteFile is a fake call that invokes WriteFileFn or just return nil.
func (f *FakeOS) WriteFile(filename string, data []byte, perm os.FileMode) error {
if err := f.getError("WriteFile"); err != nil {
return err
}
if f.WriteFileFn != nil {
return f.WriteFileFn(filename, data, perm)
}
return nil
}

View File

@ -292,10 +292,6 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3
// TODO(random-liu): [P2] Add apparmor and seccomp. // TODO(random-liu): [P2] Add apparmor and seccomp.
// TODO(random-liu): [P1] Bind mount sandbox /dev/shm.
// TODO(random-liu): [P0] Bind mount sandbox resolv.conf.
return g.Spec(), nil return g.Spec(), nil
} }
@ -307,9 +303,17 @@ func (c *criContainerdService) generateContainerMounts(sandboxRootDir string, co
mounts = append(mounts, &runtime.Mount{ mounts = append(mounts, &runtime.Mount{
ContainerPath: etcHosts, ContainerPath: etcHosts,
HostPath: getSandboxHosts(sandboxRootDir), HostPath: getSandboxHosts(sandboxRootDir),
Readonly: securityContext.ReadonlyRootfs, Readonly: securityContext.GetReadonlyRootfs(),
}) })
// TODO(random-liu): [P0] Mount sandbox resolv.config.
// Mount sandbox resolv.config.
// TODO: Need to figure out whether we should always mount it as read-only
mounts = append(mounts, &runtime.Mount{
ContainerPath: resolvConfPath,
HostPath: getResolvPath(sandboxRootDir),
Readonly: securityContext.GetReadonlyRootfs(),
})
// TODO(random-liu): [P0] Mount sandbox /dev/shm. // TODO(random-liu): [P0] Mount sandbox /dev/shm.
return mounts return mounts
} }

View File

@ -173,22 +173,24 @@ func getStartContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandboxC
func TestGeneralContainerSpec(t *testing.T) { func TestGeneralContainerSpec(t *testing.T) {
testID := "test-id" testID := "test-id"
testPodID := "test-pod-id"
testPid := uint32(1234) testPid := uint32(1234)
config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData() config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService() c := newTestCRIContainerdService()
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil) spec, err := c.generateContainerSpec(testID, testPodID, testPid, config, sandboxConfig, imageConfig, nil)
assert.NoError(t, err) assert.NoError(t, err)
specCheck(t, testID, testPid, spec) specCheck(t, testID, testPid, spec)
} }
func TestContainerSpecTty(t *testing.T) { func TestContainerSpecTty(t *testing.T) {
testID := "test-id" testID := "test-id"
testPodID := "test-pod-id"
testPid := uint32(1234) testPid := uint32(1234)
config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData() config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService() c := newTestCRIContainerdService()
for _, tty := range []bool{true, false} { for _, tty := range []bool{true, false} {
config.Tty = tty config.Tty = tty
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil) spec, err := c.generateContainerSpec(testID, testPodID, testPid, config, sandboxConfig, imageConfig, nil)
assert.NoError(t, err) assert.NoError(t, err)
specCheck(t, testID, testPid, spec) specCheck(t, testID, testPid, spec)
assert.Equal(t, tty, spec.Process.Terminal) assert.Equal(t, tty, spec.Process.Terminal)
@ -197,12 +199,13 @@ func TestContainerSpecTty(t *testing.T) {
func TestContainerSpecReadonlyRootfs(t *testing.T) { func TestContainerSpecReadonlyRootfs(t *testing.T) {
testID := "test-id" testID := "test-id"
testPodID := "test-pod-id"
testPid := uint32(1234) testPid := uint32(1234)
config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData() config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService() c := newTestCRIContainerdService()
for _, readonly := range []bool{true, false} { for _, readonly := range []bool{true, false} {
config.Linux.SecurityContext.ReadonlyRootfs = readonly config.Linux.SecurityContext.ReadonlyRootfs = readonly
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, nil) spec, err := c.generateContainerSpec(testID, testPodID, testPid, config, sandboxConfig, imageConfig, nil)
assert.NoError(t, err) assert.NoError(t, err)
specCheck(t, testID, testPid, spec) specCheck(t, testID, testPid, spec)
assert.Equal(t, readonly, spec.Root.Readonly) assert.Equal(t, readonly, spec.Root.Readonly)
@ -211,6 +214,7 @@ func TestContainerSpecReadonlyRootfs(t *testing.T) {
func TestContainerSpecWithExtraMounts(t *testing.T) { func TestContainerSpecWithExtraMounts(t *testing.T) {
testID := "test-id" testID := "test-id"
testPodID := "test-pod-id"
testPid := uint32(1234) testPid := uint32(1234)
config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData() config, sandboxConfig, imageConfig, specCheck := getStartContainerTestData()
c := newTestCRIContainerdService() c := newTestCRIContainerdService()
@ -225,7 +229,7 @@ func TestContainerSpecWithExtraMounts(t *testing.T) {
HostPath: "test-host-path-extra", HostPath: "test-host-path-extra",
Readonly: true, Readonly: true,
} }
spec, err := c.generateContainerSpec(testID, testPid, config, sandboxConfig, imageConfig, []*runtime.Mount{extraMount}) spec, err := c.generateContainerSpec(testID, testPodID, testPid, config, sandboxConfig, imageConfig, []*runtime.Mount{extraMount})
assert.NoError(t, err) assert.NoError(t, err)
specCheck(t, testID, testPid, spec) specCheck(t, testID, testPid, spec)
var mounts []runtimespec.Mount var mounts []runtimespec.Mount

View File

@ -69,6 +69,9 @@ const (
sandboxesDir = "sandboxes" sandboxesDir = "sandboxes"
// containersDir contains all container root. // containersDir contains all container root.
containersDir = "containers" containersDir = "containers"
// According to http://man7.org/linux/man-pages/man5/resolv.conf.5.html:
// "The search list is currently limited to six domains with a total of 256 characters."
maxDNSSearches = 6
// stdinNamedPipe is the name of stdin named pipe. // stdinNamedPipe is the name of stdin named pipe.
stdinNamedPipe = "stdin" stdinNamedPipe = "stdin"
// stdoutNamedPipe is the name of stdout named pipe. // stdoutNamedPipe is the name of stdout named pipe.
@ -87,6 +90,8 @@ const (
pidNSFormat = "/proc/%v/ns/pid" pidNSFormat = "/proc/%v/ns/pid"
// etcHosts is the default path of /etc/hosts file. // etcHosts is the default path of /etc/hosts file.
etcHosts = "/etc/hosts" etcHosts = "/etc/hosts"
// resolvConfPath is the abs path of resolv.conf on host or container.
resolvConfPath = "/etc/resolv.conf"
) )
// generateID generates a random unique id. // generateID generates a random unique id.
@ -149,6 +154,11 @@ func getSandboxHosts(sandboxRootDir string) string {
return filepath.Join(sandboxRootDir, "hosts") return filepath.Join(sandboxRootDir, "hosts")
} }
// getResolvPath returns resolv.conf filepath for specified sandbox.
func getResolvPath(sandboxRoot string) string {
return filepath.Join(sandboxRoot, "resolv.conf")
}
// prepareStreamingPipes prepares stream named pipe for container. returns nil // prepareStreamingPipes prepares stream named pipe for container. returns nil
// streaming handler if corresponding stream path is empty. // streaming handler if corresponding stream path is empty.
func (c *criContainerdService) prepareStreamingPipes(ctx context.Context, stdin, stdout, stderr string) ( func (c *criContainerdService) prepareStreamingPipes(ctx context.Context, stdin, stdout, stderr string) (

View File

@ -19,6 +19,7 @@ package server
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/containerd/containerd/api/services/execution" "github.com/containerd/containerd/api/services/execution"
@ -303,8 +304,55 @@ func (c *criContainerdService) setupSandboxFiles(rootDir string, config *runtime
if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0666); err != nil { if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0666); err != nil {
return fmt.Errorf("failed to generate sandbox hosts file %q: %v", sandboxEtcHosts, err) return fmt.Errorf("failed to generate sandbox hosts file %q: %v", sandboxEtcHosts, err)
} }
// TODO(random-liu): [P0] Set DNS options. Maintain a resolv.conf for the sandbox.
// Set DNS options. Maintain a resolv.conf for the sandbox.
var err error
resolvContent := ""
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
resolvContent, err = parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options)
if err != nil {
return fmt.Errorf("failed to parse sandbox DNSConfig %+v: %v", dnsConfig, err)
}
}
resolvPath := getResolvPath(rootDir)
if resolvContent == "" {
// copy host's resolv.conf to resolvPath
err = c.os.CopyFile(resolvConfPath, resolvPath, 0644)
if err != nil {
return fmt.Errorf("failed to copy host's resolv.conf to %q: %v", resolvPath, err)
}
} else {
err = c.os.WriteFile(resolvPath, []byte(resolvContent), 0644)
if err != nil {
return fmt.Errorf("failed to write resolv content to %q: %v", resolvPath, err)
}
}
// TODO(random-liu): [P0] Deal with /dev/shm. Use host for HostIpc, and create and mount for // TODO(random-liu): [P0] Deal with /dev/shm. Use host for HostIpc, and create and mount for
// non-HostIpc. What about mqueue? // non-HostIpc. What about mqueue?
return nil return nil
} }
// parseDNSOptions parse DNS options into resolv.conf format content,
// if none option is specified, will return empty with no error.
func parseDNSOptions(servers, searches, options []string) (string, error) {
resolvContent := ""
if len(searches) > maxDNSSearches {
return "", fmt.Errorf("DNSOption.Searches has more than 6 domains")
}
if len(searches) > 0 {
resolvContent += fmt.Sprintf("search %s\n", strings.Join(searches, " "))
}
if len(servers) > 0 {
resolvContent += fmt.Sprintf("nameserver %s\n", strings.Join(servers, "\nnameserver "))
}
if len(options) > 0 {
resolvContent += fmt.Sprintf("options %s\n", strings.Join(options, " "))
}
return resolvContent, nil
}

View File

@ -161,6 +161,7 @@ func TestSetupSandboxFiles(t *testing.T) {
testRootDir := "test-sandbox-root" testRootDir := "test-sandbox-root"
expectedCopys := [][]interface{}{ expectedCopys := [][]interface{}{
{"/etc/hosts", testRootDir + "/hosts", os.FileMode(0666)}, {"/etc/hosts", testRootDir + "/hosts", os.FileMode(0666)},
{"/etc/resolv.conf", testRootDir + "/resolv.conf", os.FileMode(0644)},
} }
c := newTestCRIContainerdService() c := newTestCRIContainerdService()
var copys [][]interface{} var copys [][]interface{}
@ -169,7 +170,7 @@ func TestSetupSandboxFiles(t *testing.T) {
return nil return nil
} }
c.setupSandboxFiles(testRootDir, nil) c.setupSandboxFiles(testRootDir, nil)
assert.Equal(t, expectedCopys, copys, "should copy /etc/hosts for sandbox") assert.Equal(t, expectedCopys, copys, "should copy expected files for sandbox")
} }
func TestRunPodSandbox(t *testing.T) { func TestRunPodSandbox(t *testing.T) {