diff --git a/cri/stream/remotecommand/attach.go b/cri/stream/remotecommand/attach.go index 5cb11b7fb7..f50cfe74cd 100644 --- a/cri/stream/remotecommand/attach.go +++ b/cri/stream/remotecommand/attach.go @@ -1,7 +1,6 @@ package remotecommand import ( - "fmt" "net/http" "time" ) @@ -9,7 +8,7 @@ import ( // Attacher knows how to attach a running container in a pod. type Attacher interface { // Attach attaches to the running container in the pod. - Attach() error + Attach(containerID string, streamOpts *Options, streams *Streams) error } // ServeAttach handles requests to attach to a container. After creating/receiving the required @@ -22,9 +21,10 @@ func ServeAttach(w http.ResponseWriter, req *http.Request, attacher Attacher, co } defer ctx.conn.Close() - // Hardcode to pass CI, implement it later. - fmt.Fprintf(ctx.stdoutStream, "hello\n") - - // Actuall it's a bug of cri-tools v1.0.0-alpha.0, workaround it. - time.Sleep(1 * time.Second) + attacher.Attach(container, streamOpts, &Streams{ + StreamCh: make(chan struct{}, 1), + StdinStream: ctx.stdinStream, + StdoutStream: ctx.stdoutStream, + StderrStream: ctx.stderrStream, + }) } diff --git a/cri/stream/remotecommand/exec.go b/cri/stream/remotecommand/exec.go index 6fa68c8820..9a2ffa7744 100644 --- a/cri/stream/remotecommand/exec.go +++ b/cri/stream/remotecommand/exec.go @@ -1,7 +1,6 @@ package remotecommand import ( - "fmt" "net/http" "time" ) @@ -9,7 +8,7 @@ import ( // Executor knows how to execute a command in a container of the pod. type Executor interface { // Exec executes a command in a container of the pod. - Exec() error + Exec(containerID string, cmd []string, streamOpts *Options, streams *Streams) error } // ServeExec handles requests to execute a command in a container. After @@ -23,6 +22,9 @@ func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, cont } defer ctx.conn.Close() - // Hardcode to pass CI, implement it later. - fmt.Fprintf(ctx.stdoutStream, "hello\n") + executor.Exec(container, cmd, streamOpts, &Streams{ + StdinStream: ctx.stdinStream, + StdoutStream: ctx.stdoutStream, + StderrStream: ctx.stderrStream, + }) } diff --git a/cri/stream/remotecommand/httpstream.go b/cri/stream/remotecommand/httpstream.go index 212f171a30..04360ce13c 100644 --- a/cri/stream/remotecommand/httpstream.go +++ b/cri/stream/remotecommand/httpstream.go @@ -22,6 +22,16 @@ type Options struct { TTY bool } +// Streams contains all the streams used to stdio for +// remote command execution. +type Streams struct { + // Notified from StreamCh if streams broken. + StreamCh chan struct{} + StdinStream io.ReadCloser + StdoutStream io.WriteCloser + StderrStream io.WriteCloser +} + // context contains the connection and streams used when // forwarding an attach or execute session into a container. type context struct { @@ -82,6 +92,8 @@ func createHTTPStreamStreams(w http.ResponseWriter, req *http.Request, opts *Opt case "": logrus.Infof("Client did not request protocol negotiation. Falling back to %q", constant.StreamProtocolV1Name) fallthrough + case constant.StreamProtocolV2Name: + handler = &v2ProtocolHandler{} case constant.StreamProtocolV1Name: handler = &v1ProtocolHandler{} } @@ -129,6 +141,50 @@ type protocolHandler interface { waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) } +// v2ProtocolHandler implements the V2 protocol version for streaming command execution. +type v2ProtocolHandler struct{} + +func (*v2ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) { + ctx := &context{} + receivedStreams := 0 + replyChan := make(chan struct{}) + stop := make(chan struct{}) + defer close(stop) +WaitForStreams: + for { + select { + case stream := <-streams: + streamType := stream.Headers().Get(constant.StreamType) + switch streamType { + case constant.StreamTypeError: + go waitStreamReply(stream.replySent, replyChan, stop) + case constant.StreamTypeStdin: + ctx.stdinStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case constant.StreamTypeStdout: + ctx.stdoutStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case constant.StreamTypeStderr: + ctx.stderrStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + default: + logrus.Errorf("Unexpected stream type: %q", streamType) + } + case <-replyChan: + receivedStreams++ + if receivedStreams == expectedStreams { + break WaitForStreams + } + case <-expired: + // TODO find a way to return the error to the user. Maybe use a separate + // stream to report errors? + return nil, fmt.Errorf("timed out waiting for client to create streams") + } + } + + return ctx, nil +} + // v1ProtocolHandler implements the V1 protocol version for streaming command execution. type v1ProtocolHandler struct{} diff --git a/cri/stream/server.go b/cri/stream/server.go index 846b2ad9b1..b5d3a84569 100644 --- a/cri/stream/server.go +++ b/cri/stream/server.go @@ -30,7 +30,7 @@ const ( // TODO: StreamProtocolV2Name, StreamProtocolV3Name, StreamProtocolV4Name support. // SupportedStreamingProtocols is the streaming protocols which server supports. -var SupportedStreamingProtocols = []string{constant.StreamProtocolV1Name} +var SupportedStreamingProtocols = []string{constant.StreamProtocolV1Name, constant.StreamProtocolV2Name} // SupportedPortForwardProtocols is the portforward protocols which server supports. var SupportedPortForwardProtocols = []string{constant.PortForwardProtocolV1Name} @@ -53,10 +53,10 @@ type Server interface { // Runtime is the interface to execute the commands and provide the streams. type Runtime interface { // Exec executes the command in pod. - Exec() error + Exec(containerID string, cmd []string, streamOpts *remotecommand.Options, streams *remotecommand.Streams) error // Attach attaches to pod. - Attach() error + Attach(containerID string, streamOpts *remotecommand.Options, streams *remotecommand.Streams) error // PortForward forward port to pod. PortForward(name string, port int32, stream io.ReadWriteCloser) error diff --git a/daemon/containerio/container_io.go b/daemon/containerio/container_io.go index f45bc03f13..fe9b9d29a1 100644 --- a/daemon/containerio/container_io.go +++ b/daemon/containerio/container_io.go @@ -50,6 +50,19 @@ func NewIO(opt *Option) *IO { } } +// AddBackend adds more backends to container's stdio. +func (io *IO) AddBackend(opt *Option) { + backends := createBackend(opt) + + for t, s := range map[stdioType]*ContainerIO{ + stdin: io.Stdin, + stdout: io.Stdout, + stderr: io.Stderr, + } { + s.add(opt, t, backends) + } +} + // Close closes the container's io. func (io *IO) Close() error { io.Stderr.Close() @@ -61,30 +74,59 @@ func (io *IO) Close() error { // ContainerIO used to control the container's stdio. type ContainerIO struct { Option - backends map[string]containerBackend + backends []containerBackend total int64 typ stdioType closed bool + // The stdin of all backends should put into ring first. + ring *ringbuff.RingBuff +} + +func (cio *ContainerIO) add(opt *Option, typ stdioType, backends map[string]containerBackend) { + if typ == stdin { + for _, b := range backends { + if b.backend.Name() == opt.stdinBackend { + cio.backends = append(cio.backends, b) + go func(b containerBackend) { + cio.converge(b.backend.Name(), opt.id, b.backend.In()) + b.backend.Close() + }(b) + break + } + } + } else { + for _, b := range backends { + cio.backends = append(cio.backends, b) + } + } } func create(opt *Option, typ stdioType, backends map[string]containerBackend) *ContainerIO { io := &ContainerIO{ - backends: backends, - total: 0, - typ: typ, - closed: false, - Option: *opt, + total: 0, + typ: typ, + closed: false, + Option: *opt, } if typ == stdin { - io.backends = make(map[string]containerBackend) - + io.ring = ringbuff.New(10) for _, b := range backends { if b.backend.Name() == opt.stdinBackend { - io.backends[opt.stdinBackend] = b + io.backends = append(io.backends, b) + go func(b containerBackend) { + // For backend with stdin, close it if stdin finished. + io.converge(b.backend.Name(), opt.id, b.backend.In()) + b.backend.Close() + b.ring.Close() + }(b) break } } + } else { + for _, b := range backends { + io.backends = append(io.backends, b) + } } return io @@ -124,51 +166,51 @@ func createBackend(opt *Option) map[string]containerBackend { } // OpenStdin returns open container's stdin or not. -func (io *ContainerIO) OpenStdin() bool { - if io.typ != stdin { +func (cio *ContainerIO) OpenStdin() bool { + if cio.typ != stdin { return false } - if io.closed { + if cio.closed { return false } - return len(io.backends) != 0 + return len(cio.backends) != 0 } // Read implements the standard Read interface. -func (io *ContainerIO) Read(p []byte) (int, error) { - if io.typ != stdin { - return 0, fmt.Errorf("invalid container io type: %s, id: %s", io.typ, io.id) +func (cio *ContainerIO) Read(p []byte) (int, error) { + if cio.typ != stdin { + return 0, fmt.Errorf("invalid container io type: %s, id: %s", cio.typ, cio.id) } - if io.closed { + if cio.closed { return 0, fmt.Errorf("container io is closed") } - if len(io.backends) == 0 { - block := make(chan struct{}) - <-block + value, _ := cio.ring.Pop() + data, ok := value.([]byte) + if !ok { + return 0, nil } + n := copy(p, data) - backend := io.backends[io.stdinBackend] - - return backend.backend.In().Read(p) + return n, nil } // Write implements the standard Write interface. -func (io *ContainerIO) Write(data []byte) (int, error) { - if io.typ == stdin { - return 0, fmt.Errorf("invalid container io type: %s, id: %s", io.typ, io.id) +func (cio *ContainerIO) Write(data []byte) (int, error) { + if cio.typ == stdin { + return 0, fmt.Errorf("invalid container io type: %s, id: %s", cio.typ, cio.id) } - if io.closed { + if cio.closed { return 0, fmt.Errorf("container io is closed") } - if io.typ == discard { + if cio.typ == discard { return len(data), nil } - for _, b := range io.backends { + for _, b := range cio.backends { if cover := b.ring.Push(data); cover { - logrus.Warnf("cover data, backend: %s, id: %s", b.backend.Name(), io.id) + logrus.Warnf("cover data, backend: %s, id: %s", b.backend.Name(), cio.id) } } @@ -176,17 +218,18 @@ func (io *ContainerIO) Write(data []byte) (int, error) { } // Close implements the standard Close interface. -func (io *ContainerIO) Close() error { - for name, b := range io.backends { +func (cio *ContainerIO) Close() error { + for _, b := range cio.backends { // we need to close ringbuf before close backend, because close ring will flush // the remain data into backend. + name := b.backend.Name() b.ring.Close() b.backend.Close() - logrus.Infof("close containerio backend: %s, id: %s", name, io.id) + logrus.Infof("close containerio backend: %s, id: %s", name, cio.id) } - io.closed = true + cio.closed = true return nil } @@ -215,3 +258,24 @@ func subscribe(name, id string, ring *ringbuff.RingBuff, out io.Writer) { logrus.Infof("finished to subscribe io, backend: %s, id: %s", name, id) } + +// converge be called in a goroutine. +func (cio *ContainerIO) converge(name, id string, in io.Reader) { + // TODO: we should implement this function more elegant and robust. + logrus.Infof("start to converge io, backend: %s, id: %s", name, id) + + data := make([]byte, 128) + for { + n, err := in.Read(data) + if err != nil { + logrus.Errorf("failed to read from backend: %s, id: %s, %v", name, id, err) + break + } + cover := cio.ring.Push(data[:n]) + if cover { + logrus.Warnf("cover data, backend: %s, id: %s", name, id) + } + } + + logrus.Infof("finished to converge io, backend: %s, id: %s", name, id) +} diff --git a/daemon/containerio/options.go b/daemon/containerio/options.go index d4907ba61f..7cb3b80916 100644 --- a/daemon/containerio/options.go +++ b/daemon/containerio/options.go @@ -3,6 +3,8 @@ package containerio import ( "bytes" "net/http" + + "github.com/alibaba/pouch/cri/stream/remotecommand" ) // Option is used to pass some data into ContainerIO. @@ -14,6 +16,7 @@ type Option struct { hijackUpgrade bool stdinBackend string memBuffer *bytes.Buffer + streams *remotecommand.Streams } // NewOption creates the Option instance. @@ -90,3 +93,21 @@ func WithMemBuffer(memBuffer *bytes.Buffer) func(*Option) { opt.memBuffer = memBuffer } } + +// WithStreams specified the stream backend. +func WithStreams(streams *remotecommand.Streams) func(*Option) { + return func(opt *Option) { + if opt.backends == nil { + opt.backends = make(map[string]struct{}) + } + opt.backends["streams"] = struct{}{} + opt.streams = streams + } +} + +// WithStdinStream specified the stdin with stream. +func WithStdinStream() func(*Option) { + return func(opt *Option) { + opt.stdinBackend = "streams" + } +} diff --git a/daemon/containerio/streams.go b/daemon/containerio/streams.go new file mode 100644 index 0000000000..f4fde64345 --- /dev/null +++ b/daemon/containerio/streams.go @@ -0,0 +1,60 @@ +package containerio + +import ( + "io" + + "github.com/alibaba/pouch/cri/stream/remotecommand" +) + +func init() { + Register(func() Backend { + return &streamIO{} + }) +} + +type streamIO struct { + streams *remotecommand.Streams + closed bool +} + +func (s *streamIO) Name() string { + return "streams" +} + +func (s *streamIO) Init(opt *Option) error { + s.streams = opt.streams + + return nil +} + +func (s *streamIO) Out() io.Writer { + return s.streams.StdoutStream +} + +func (s *streamIO) In() io.Reader { + return s.streams.StdinStream +} + +func (s *streamIO) Close() error { + if s.closed { + return nil + } + + for _, closer := range []io.Closer{ + s.streams.StdinStream, + s.streams.StdoutStream, + s.streams.StderrStream, + } { + if closer != nil { + closer.Close() + } + } + + if s.streams.StreamCh != nil { + s.streams.StreamCh <- struct{}{} + } + + s.closed = true + + return nil +} diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index b98b8291b8..cb487a2f15 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -654,7 +654,8 @@ func (mgr *ContainerManager) Attach(ctx context.Context, name string, attach *At return err } - if _, err := mgr.openContainerIO(c.ID(), attach); err != nil { + _, err = mgr.openAttachIO(c.ID(), attach) + if err != nil { return err } @@ -855,6 +856,43 @@ func (mgr *ContainerManager) openContainerIO(id string, attach *AttachConfig) (* return mgr.openIO(id, attach, false) } +func (mgr *ContainerManager) openAttachIO(id string, attach *AttachConfig) (*containerio.IO, error) { + io := mgr.IOs.Get(id) + if io == nil { + return mgr.openIO(id, attach, false) + } + + options := []func(*containerio.Option){ + containerio.WithID(id), + } + if attach != nil { + if attach.Hijack != nil { + // Attaching using http. + options = append(options, containerio.WithHijack(attach.Hijack, attach.Upgrade)) + if attach.Stdin { + options = append(options, containerio.WithStdinHijack()) + } + } else if attach.MemBuffer != nil { + // Attaching using memory buffer. + options = append(options, containerio.WithMemBuffer(attach.MemBuffer)) + } else if attach.Streams != nil { + // Attaching using streams. + options = append(options, containerio.WithStreams(attach.Streams)) + if attach.Stdin { + options = append(options, containerio.WithStdinStream()) + } + } + } else { + options = append(options, containerio.WithDiscard()) + } + + io.AddBackend(containerio.NewOption(options...)) + + mgr.IOs.Put(id, io) + + return io, nil +} + func (mgr *ContainerManager) openExecIO(id string, attach *AttachConfig) (*containerio.IO, error) { return mgr.openIO(id, attach, true) } @@ -883,6 +921,12 @@ func (mgr *ContainerManager) openIO(id string, attach *AttachConfig, exec bool) } else if attach.MemBuffer != nil { // Attaching using memory buffer. options = append(options, containerio.WithMemBuffer(attach.MemBuffer)) + } else if attach.Streams != nil { + // Attaching using streams. + options = append(options, containerio.WithStreams(attach.Streams)) + if attach.Stdin { + options = append(options, containerio.WithStdinStream()) + } } } else if !exec { options = append(options, containerio.WithRawFile()) diff --git a/daemon/mgr/container_types.go b/daemon/mgr/container_types.go index a88984a5df..f04c736d8a 100644 --- a/daemon/mgr/container_types.go +++ b/daemon/mgr/container_types.go @@ -7,6 +7,7 @@ import ( "time" "github.com/alibaba/pouch/apis/types" + "github.com/alibaba/pouch/cri/stream/remotecommand" "github.com/alibaba/pouch/ctrd" "github.com/alibaba/pouch/pkg/meta" "github.com/alibaba/pouch/pkg/utils" @@ -51,6 +52,9 @@ type AttachConfig struct { // Attach using memory buffer. MemBuffer *bytes.Buffer + + // Attach using streams. + Streams *remotecommand.Streams } // ContainerRemoveOption wraps the container remove interface params. diff --git a/daemon/mgr/cri.go b/daemon/mgr/cri.go index 7114942148..72acd3bc34 100644 --- a/daemon/mgr/cri.go +++ b/daemon/mgr/cri.go @@ -51,6 +51,9 @@ const ( namespaceModeHost = "host" namespaceModeNone = "none" + + // resolvConfPath is the abs path of resolv.conf on host or container. + resolvConfPath = "/etc/resolv.conf" ) var ( @@ -78,6 +81,9 @@ type CriManager struct { // StreamServer is the stream server of CRI serves container streaming request. StreamServer stream.Server + + // SandboxBaseDir is the directory used to store sandbox files like /etc/hosts, /etc/resolv.conf, etc. + SandboxBaseDir string } // NewCriManager creates a brand new cri manager. @@ -92,6 +98,7 @@ func NewCriManager(config *config.Config, ctrMgr ContainerMgr, imgMgr ImageMgr) ImageMgr: imgMgr, CniMgr: NewCniManager(&config.CriConfig), StreamServer: streamServer, + SandboxBaseDir: path.Join(config.HomeDir, "sandboxes"), } return NewCriWrapper(c), nil @@ -149,6 +156,18 @@ func (c *CriManager) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox return nil, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.Name, err) } + sandboxRootDir := path.Join(c.SandboxBaseDir, id) + err = os.MkdirAll(sandboxRootDir, 0755) + if err != nil { + return nil, fmt.Errorf("failed to create sandbox root directory: %v", err) + } + + // Setup sandbox file /etc/resolv.conf. + err = setupSandboxFiles(sandboxRootDir, config) + if err != nil { + return nil, fmt.Errorf("failed to setup sandbox files: %v", err) + } + // Step 4: Setup networking for the sandbox. container, err := c.ContainerMgr.Get(ctx, id) if err != nil { @@ -260,6 +279,13 @@ func (c *CriManager) RemovePodSandbox(ctx context.Context, r *runtime.RemovePodS return nil, fmt.Errorf("failed to remove sandbox %q: %v", podSandboxID, err) } + // Cleanup the sandbox root directory. + sandboxRootDir := path.Join(c.SandboxBaseDir, podSandboxID) + err = os.RemoveAll(sandboxRootDir) + if err != nil { + return nil, fmt.Errorf("failed to remove root directory %q: %v", sandboxRootDir, err) + } + return &runtime.RemovePodSandboxResponse{}, nil } @@ -380,6 +406,10 @@ func (c *CriManager) CreateContainer(ctx context.Context, r *runtime.CreateConta return nil, err } + // Bindings to overwrite the container's /etc/resolv.conf, /etc/hosts etc. + sandboxRootDir := path.Join(c.SandboxBaseDir, podSandboxID) + createConfig.HostConfig.Binds = append(createConfig.HostConfig.Binds, generateContainerMounts(sandboxRootDir)) + // TODO: devices and security option configurations. containerName := makeContainerName(sandboxConfig, config) diff --git a/daemon/mgr/cri_stream.go b/daemon/mgr/cri_stream.go index 22ad192a40..94da9db7b3 100644 --- a/daemon/mgr/cri_stream.go +++ b/daemon/mgr/cri_stream.go @@ -9,7 +9,9 @@ import ( "os/exec" "strings" + apitypes "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/cri/stream" + "github.com/alibaba/pouch/cri/stream/remotecommand" "github.com/sirupsen/logrus" ) @@ -30,13 +32,60 @@ func newStreamRuntime(ctrMgr ContainerMgr) stream.Runtime { } // Exec executes a command inside the container. -func (s *streamRuntime) Exec() error { - return fmt.Errorf("streamRuntime's Exec Not Implemented Yet") +func (s *streamRuntime) Exec(containerID string, cmd []string, streamOpts *remotecommand.Options, streams *remotecommand.Streams) error { + createConfig := &apitypes.ExecCreateConfig{ + Cmd: cmd, + AttachStdin: streamOpts.Stdin, + AttachStdout: streamOpts.Stdout, + AttachStderr: streamOpts.Stderr, + Tty: streamOpts.TTY, + } + + ctx := context.Background() + + execid, err := s.containerMgr.CreateExec(ctx, containerID, createConfig) + if err != nil { + return fmt.Errorf("failed to create exec for container %q: %v", containerID, err) + } + + startConfig := &apitypes.ExecStartConfig{} + attachConfig := &AttachConfig{ + Streams: streams, + } + + err = s.containerMgr.StartExec(ctx, execid, startConfig, attachConfig) + if err != nil { + return fmt.Errorf("failed to start exec for container %q: %v", containerID, err) + } + + ei, err := s.containerMgr.InspectExec(ctx, execid) + if err != nil { + return fmt.Errorf("failed to inspect exec for container %q: %v", containerID, err) + } + + // Not return until exec finished. + <-ei.ExitCh + + return nil } // Attach attaches to a running container. -func (s *streamRuntime) Attach() error { - return fmt.Errorf("streamRuntime's Attach Not Implemented Yet") +func (s *streamRuntime) Attach(containerID string, streamOpts *remotecommand.Options, streams *remotecommand.Streams) error { + attachConfig := &AttachConfig{ + Stdin: streamOpts.Stdin, + Stdout: streamOpts.Stdout, + Stderr: streamOpts.Stderr, + Streams: streams, + } + + err := s.containerMgr.Attach(context.Background(), containerID, attachConfig) + if err != nil { + return fmt.Errorf("failed to attach to container %q: %v", containerID, err) + } + + <-streams.StreamCh + + return nil } // PortForward forwards ports from a PodSandbox. diff --git a/daemon/mgr/cri_utils.go b/daemon/mgr/cri_utils.go index 048d5138c4..bcac580d85 100644 --- a/daemon/mgr/cri_utils.go +++ b/daemon/mgr/cri_utils.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" "time" + "ioutil" apitypes "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/pkg/reference" @@ -91,6 +92,18 @@ func extractLabels(input map[string]string) (map[string]string, map[string]strin return labels, annotations } +// generateContainerMounts sets up necessary container mounts including /dev/shm, /etc/hosts +// and /etc/resolv.conf. +func generateContainerMounts(sandboxRootDir string) []string { + // TODO: more attr and check whether these bindings is included in cri mounts. + result := []string{} + hostPath := path.Join(sandboxRootDir, "resolv.conf") + containerPath := resolvConfPath + result = append(result, fmt.Sprintf("%s:%s", hostPath, containerPath)) + + return result +} + func generateMountBindings(mounts []*runtime.Mount) []string { result := make([]string, 0, len(mounts)) for _, m := range mounts { @@ -300,6 +313,74 @@ func filterCRISandboxes(sandboxes []*runtime.PodSandbox, filter *runtime.PodSand return filtered } +// 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) > 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 +} + +// copyFile copys src file to dest file +func copyFile(src, dest string, perm os.FileMode) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + return err +} + +// setupSandboxFiles sets up necessary sandbox files. +func setupSandboxFiles(sandboxRootDir string, config *runtime.PodSandboxConfig) error { + // Set DNS options. Maintain a resolv.conf for the sandbox. + var resolvContent string + resolvPath := path.Join(sandboxRootDir, "resolv.conf") + + dnsConfig := config.GetDnsConfig() + if 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) + } + } + + if resolvContent == "" { + // Copy host's resolv.conf to resolvPath. + err = copyFile(resolvConfPath, resolvPath, 0644) + if err != nil { + return fmt.Errorf("failed to copy host's resolv.conf to %q: %v", resolvPath, err) + } + } else { + err = ioutil.WriteFile(resolvPath, []byte(resolvContent), 0644) + if err != nil { + return fmt.Errorf("failed to write resolv content to %q: %v", resolvPath, err) + } + } + + return nil +} + // Container related tool functions. func makeContainerName(s *runtime.PodSandboxConfig, c *runtime.ContainerConfig) string { diff --git a/hack/cri-test/test-cri.sh b/hack/cri-test/test-cri.sh index a650b32e21..3046b10724 100755 --- a/hack/cri-test/test-cri.sh +++ b/hack/cri-test/test-cri.sh @@ -26,7 +26,7 @@ POUCH_SOCK="/var/run/pouchcri.sock" CRI_FOCUS=${CRI_FOCUS:-"PodSandbox|AppArmor|Privileged is true|basic operations on container|Runtime info|mount propagation|volume and device|RunAsUser|container port|Streaming|NamespaceOption|SupplementalGroups"} # CRI_SKIP skips the test to skip. -CRI_SKIP=${CRI_SKIP:-"RunAsUserName|attach|HostNetwork"} +CRI_SKIP=${CRI_SKIP:-"RunAsUserName|HostNetwork"} # REPORT_DIR is the the directory to store test logs. REPORT_DIR=${REPORT_DIR:-"/tmp/test-cri"}