123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- package docker
- import (
- "encoding/json"
- "errors"
- "io"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path"
- "syscall"
- "time"
- )
- type Container struct {
- Id string
- Root string
- Created time.Time
- Path string
- Args []string
- Config *Config
- Filesystem *Filesystem
- State *State
- lxcConfigPath string
- cmd *exec.Cmd
- stdout *writeBroadcaster
- stderr *writeBroadcaster
- }
- type Config struct {
- Hostname string
- Ram int64
- }
- func createContainer(id string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
- container := &Container{
- Id: id,
- Root: root,
- Created: time.Now(),
- Path: command,
- Args: args,
- Config: config,
- Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
- State: newState(),
- lxcConfigPath: path.Join(root, "config.lxc"),
- stdout: newWriteBroadcaster(),
- stderr: newWriteBroadcaster(),
- }
- if err := os.Mkdir(root, 0700); err != nil {
- return nil, err
- }
- if err := container.save(); err != nil {
- return nil, err
- }
- if err := container.generateLXCConfig(); err != nil {
- return nil, err
- }
- return container, nil
- }
- func loadContainer(containerPath string) (*Container, error) {
- data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
- if err != nil {
- return nil, err
- }
- container := &Container{}
- if err := json.Unmarshal(data, container); err != nil {
- return nil, err
- }
- return container, nil
- }
- func (container *Container) loadUserData() (map[string]string, error) {
- jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json"))
- if err != nil {
- if os.IsNotExist(err) {
- return make(map[string]string), nil
- }
- return nil, err
- }
- data := make(map[string]string)
- if err := json.Unmarshal(jsonData, &data); err != nil {
- return nil, err
- }
- return data, nil
- }
- func (container *Container) saveUserData(data map[string]string) error {
- jsonData, err := json.Marshal(data)
- if err != nil {
- return err
- }
- return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700)
- }
- func (container *Container) SetUserData(key, value string) error {
- data, err := container.loadUserData()
- if err != nil {
- return err
- }
- data[key] = value
- return container.saveUserData(data)
- }
- func (container *Container) GetUserData(key string) (string) {
- data, err := container.loadUserData()
- if err != nil {
- return ""
- }
- if value, exists := data[key]; exists {
- return value
- }
- return ""
- }
- func (container *Container) save() (err error) {
- data, err := json.Marshal(container)
- if err != nil {
- return
- }
- return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0700)
- }
- func (container *Container) generateLXCConfig() error {
- fo, err := os.Create(container.lxcConfigPath)
- if err != nil {
- return err
- }
- defer fo.Close()
- if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
- return err
- }
- return nil
- }
- func (container *Container) Start() error {
- if err := container.Filesystem.Mount(); err != nil {
- return err
- }
- params := []string{
- "-n", container.Id,
- "-f", container.lxcConfigPath,
- "--",
- container.Path,
- }
- params = append(params, container.Args...)
- container.cmd = exec.Command("/usr/bin/lxc-start", params...)
- container.cmd.Stdout = container.stdout
- container.cmd.Stderr = container.stderr
- if err := container.cmd.Start(); err != nil {
- return err
- }
- container.State.setRunning(container.cmd.Process.Pid)
- container.save()
- go container.monitor()
- if err := exec.Command("/usr/bin/lxc-wait", "-n", container.Id, "-s", "RUNNING|STOPPED").Run(); err != nil {
- // lxc-wait might return an error if by the time we call it,
- // the container we just started is already STOPPED.
- // This is a rare race condition that happens for short living programs.
- //
- // A workaround is to discard lxc-wait errors if the container is not
- // running anymore.
- if !container.State.Running {
- return nil
- }
- return errors.New("Container failed to start")
- }
- return nil
- }
- func (container *Container) Run() error {
- if err := container.Start(); err != nil {
- return err
- }
- container.Wait()
- return nil
- }
- func (container *Container) Output() (output []byte, err error) {
- pipe, err := container.StdoutPipe()
- if err != nil {
- return nil, err
- }
- defer pipe.Close()
- if err := container.Start(); err != nil {
- return nil, err
- }
- output, err = ioutil.ReadAll(pipe)
- container.Wait()
- return output, err
- }
- func (container *Container) StdoutPipe() (io.ReadCloser, error) {
- reader, writer := io.Pipe()
- container.stdout.AddWriter(writer)
- return newBufReader(reader), nil
- }
- func (container *Container) StderrPipe() (io.ReadCloser, error) {
- reader, writer := io.Pipe()
- container.stderr.AddWriter(writer)
- return newBufReader(reader), nil
- }
- func (container *Container) monitor() {
- // Wait for the program to exit
- container.cmd.Wait()
- exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
- // Cleanup
- container.stdout.Close()
- container.stderr.Close()
- if err := container.Filesystem.Umount(); err != nil {
- log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
- }
- // Report status back
- container.State.setStopped(exitCode)
- container.save()
- }
- func (container *Container) kill() error {
- // This will cause the main container process to receive a SIGKILL
- if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Id).Run(); err != nil {
- log.Printf("Failed to lxc-stop %v", container.Id)
- return err
- }
- // Wait for the container to be actually stopped
- container.Wait()
- // Make sure the underlying LXC thinks it's stopped too
- // LXC Issue: lxc-wait MIGHT say that the container doesn't exist
- // That's probably because it was destroyed and it cannot find it anymore
- // We are going to ignore lxc-wait's error
- exec.Command("/usr/bin/lxc-wait", "-n", container.Id, "-s", "STOPPED").Run()
- return nil
- }
- func (container *Container) Kill() error {
- if !container.State.Running {
- return nil
- }
- return container.kill()
- }
- func (container *Container) Stop() error {
- if !container.State.Running {
- return nil
- }
- // 1. Send a SIGTERM
- if err := exec.Command("/usr/bin/lxc-kill", "-n", container.Id, "15").Run(); err != nil {
- return err
- }
- // 2. Wait for the process to exit on its own
- if err := container.WaitTimeout(10 * time.Second); err != nil {
- log.Printf("Container %v failed to exit within 10 seconds of SIGTERM", container.Id)
- }
- // 3. Force kill
- if err := container.kill(); err != nil {
- return err
- }
- return nil
- }
- func (container *Container) Restart() error {
- if err := container.Stop(); err != nil {
- return err
- }
- if err := container.Start(); err != nil {
- return err
- }
- return nil
- }
- func (container *Container) Wait() {
- for container.State.Running {
- container.State.wait()
- }
- }
- func (container *Container) WaitTimeout(timeout time.Duration) error {
- done := make(chan bool)
- go func() {
- container.Wait()
- done <- true
- }()
- select {
- case <-time.After(timeout):
- return errors.New("Timed Out")
- case <-done:
- return nil
- }
- return nil
- }
|