|
@@ -14,14 +14,17 @@ import (
|
|
|
)
|
|
|
|
|
|
type Container struct {
|
|
|
- Name string
|
|
|
+ Id string
|
|
|
Root string
|
|
|
+
|
|
|
+ Created time.Time
|
|
|
+
|
|
|
Path string
|
|
|
Args []string
|
|
|
|
|
|
- *Config
|
|
|
- *Filesystem
|
|
|
- *State
|
|
|
+ Config *Config
|
|
|
+ Filesystem *Filesystem
|
|
|
+ State *State
|
|
|
|
|
|
lxcConfigPath string
|
|
|
cmd *exec.Cmd
|
|
@@ -34,10 +37,11 @@ type Config struct {
|
|
|
Ram int64
|
|
|
}
|
|
|
|
|
|
-func createContainer(name string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
|
|
|
+func createContainer(id string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
|
|
|
container := &Container{
|
|
|
- Name: name,
|
|
|
+ Id: id,
|
|
|
Root: root,
|
|
|
+ Created: time.Now(),
|
|
|
Path: command,
|
|
|
Args: args,
|
|
|
Config: config,
|
|
@@ -52,7 +56,6 @@ func createContainer(name string, root string, command string, args []string, la
|
|
|
if err := os.Mkdir(root, 0700); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
-
|
|
|
if err := container.save(); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
@@ -63,32 +66,23 @@ func createContainer(name string, root string, command string, args []string, la
|
|
|
}
|
|
|
|
|
|
func loadContainer(containerPath string) (*Container, error) {
|
|
|
- configPath := path.Join(containerPath, "config.json")
|
|
|
- fi, err := os.Open(configPath)
|
|
|
+ data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
- defer fi.Close()
|
|
|
- enc := json.NewDecoder(fi)
|
|
|
- container := &Container{}
|
|
|
- if err := enc.Decode(container); err != nil {
|
|
|
+ var container *Container
|
|
|
+ if err := json.Unmarshal(data, container); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
return container, nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) save() error {
|
|
|
- configPath := path.Join(container.Root, "config.json")
|
|
|
- fo, err := os.Create(configPath)
|
|
|
+func (container *Container) save() (err error) {
|
|
|
+ data, err := json.Marshal(container)
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
+ return
|
|
|
}
|
|
|
- defer fo.Close()
|
|
|
- enc := json.NewEncoder(fo)
|
|
|
- if err := enc.Encode(container); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- return nil
|
|
|
+ return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0700)
|
|
|
}
|
|
|
|
|
|
func (container *Container) generateLXCConfig() error {
|
|
@@ -110,7 +104,7 @@ func (container *Container) Start() error {
|
|
|
}
|
|
|
|
|
|
params := []string{
|
|
|
- "-n", container.Name,
|
|
|
+ "-n", container.Id,
|
|
|
"-f", container.lxcConfigPath,
|
|
|
"--",
|
|
|
container.Path,
|
|
@@ -125,21 +119,21 @@ func (container *Container) Start() error {
|
|
|
return err
|
|
|
}
|
|
|
container.State.setRunning(container.cmd.Process.Pid)
|
|
|
+ container.save()
|
|
|
go container.monitor()
|
|
|
-
|
|
|
- // Wait until we are out of the STARTING state before returning
|
|
|
- //
|
|
|
- // Even though lxc-wait blocks until the container reaches a given state,
|
|
|
- // sometimes it returns an error code, which is why we have to retry.
|
|
|
- //
|
|
|
- // This is a rare race condition that happens for short lived programs
|
|
|
- for retries := 0; retries < 3; retries++ {
|
|
|
- err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "RUNNING|STOPPED").Run()
|
|
|
- if err == nil {
|
|
|
+ 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 errors.New("Container failed to start")
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
func (container *Container) Run() error {
|
|
@@ -177,30 +171,37 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
|
|
}
|
|
|
|
|
|
func (container *Container) monitor() {
|
|
|
+ // Wait for the program to exit
|
|
|
container.cmd.Wait()
|
|
|
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
|
|
|
|
|
- // Cleanup container
|
|
|
+ // Cleanup
|
|
|
container.stdout.Close()
|
|
|
container.stderr.Close()
|
|
|
if err := container.Filesystem.Umount(); err != nil {
|
|
|
- log.Printf("%v: Failed to umount filesystem: %v", container.Name, err)
|
|
|
+ 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.Name).Run(); err != nil {
|
|
|
+ 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
|
|
|
- if err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "STOPPED").Run(); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
+ 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
|
|
|
}
|
|
|
|
|
@@ -217,13 +218,13 @@ func (container *Container) Stop() error {
|
|
|
}
|
|
|
|
|
|
// 1. Send a SIGTERM
|
|
|
- if err := exec.Command("/usr/bin/lxc-kill", "-n", container.Name, "15").Run(); err != nil {
|
|
|
+ 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.Name)
|
|
|
+ log.Printf("Container %v failed to exit within 10 seconds of SIGTERM", container.Id)
|
|
|
}
|
|
|
|
|
|
// 3. Force kill
|
|
@@ -233,6 +234,16 @@ func (container *Container) Stop() error {
|
|
|
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()
|