diff --git a/cgroups/cgutil/cgutil.go b/cgroups/cgutil/cgutil.go new file mode 100644 index 000000000..f4a541eab --- /dev/null +++ b/cgroups/cgutil/cgutil.go @@ -0,0 +1,264 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "syscall" + "time" + + "github.com/codegangsta/cli" + "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/cgroups/fs" + "github.com/docker/libcontainer/cgroups/systemd" +) + +var createCommand = cli.Command{ + Name: "create", + Usage: "Create a cgroup container using the supplied configuration and initial process.", + Flags: []cli.Flag{ + cli.StringFlag{"config, c", "cgroup.json", "path to container configuration (cgroups.Cgroup object)"}, + cli.IntFlag{"pid, p", 0, "pid of the initial process in the container"}, + }, + Action: createAction, +} + +var destroyCommand = cli.Command{ + Name: "destroy", + Usage: "Destroy an existing cgroup container.", + Flags: []cli.Flag{ + cli.StringFlag{"name, n", "", "container name"}, + cli.StringFlag{"parent, p", "", "container parent"}, + }, + Action: destroyAction, +} + +var statsCommand = cli.Command{ + Name: "stats", + Usage: "Get stats for cgroup", + Flags: []cli.Flag{ + cli.StringFlag{"name, n", "", "container name"}, + cli.StringFlag{"parent, p", "", "container parent"}, + }, + Action: statsAction, +} + +var pauseCommand = cli.Command{ + Name: "pause", + Usage: "Pause cgroup", + Flags: []cli.Flag{ + cli.StringFlag{"name, n", "", "container name"}, + cli.StringFlag{"parent, p", "", "container parent"}, + }, + Action: pauseAction, +} + +var resumeCommand = cli.Command{ + Name: "resume", + Usage: "Resume a paused cgroup", + Flags: []cli.Flag{ + cli.StringFlag{"name, n", "", "container name"}, + cli.StringFlag{"parent, p", "", "container parent"}, + }, + Action: resumeAction, +} + +var psCommand = cli.Command{ + Name: "ps", + Usage: "Get list of pids for a cgroup", + Flags: []cli.Flag{ + cli.StringFlag{"name, n", "", "container name"}, + cli.StringFlag{"parent, p", "", "container parent"}, + }, + Action: psAction, +} + +func getConfigFromFile(c *cli.Context) (*cgroups.Cgroup, error) { + f, err := os.Open(c.String("config")) + if err != nil { + return nil, err + } + defer f.Close() + + var config *cgroups.Cgroup + if err := json.NewDecoder(f).Decode(&config); err != nil { + log.Fatal(err) + } + return config, nil +} + +func openLog(name string) error { + f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + return err + } + + log.SetOutput(f) + return nil +} + +func getConfig(context *cli.Context) (*cgroups.Cgroup, error) { + name := context.String("name") + if name == "" { + log.Fatal(fmt.Errorf("Missing container name")) + } + parent := context.String("parent") + return &cgroups.Cgroup{ + Name: name, + Parent: parent, + }, nil +} + +func killAll(config *cgroups.Cgroup) { + // We could use freezer here to prevent process spawning while we are trying + // to kill everything. But going with more portable solution of retrying for + // now. + pids := getPids(config) + retry := 10 + for len(pids) != 0 || retry > 0 { + killPids(pids) + time.Sleep(100 * time.Millisecond) + retry-- + pids = getPids(config) + } + if len(pids) != 0 { + log.Fatal(fmt.Errorf("Could not kill existing processes in the container.")) + } +} + +func getPids(config *cgroups.Cgroup) []int { + pids, err := fs.GetPids(config) + if err != nil { + log.Fatal(err) + } + return pids +} + +func killPids(pids []int) { + for _, pid := range pids { + // pids might go away on their own. Ignore errors. + syscall.Kill(pid, syscall.SIGKILL) + } +} + +func setFreezerState(context *cli.Context, state cgroups.FreezerState) { + config, err := getConfig(context) + if err != nil { + log.Fatal(err) + } + + if systemd.UseSystemd() { + err = systemd.Freeze(config, state) + } else { + err = fs.Freeze(config, state) + } + if err != nil { + log.Fatal(err) + } +} + +func createAction(context *cli.Context) { + config, err := getConfigFromFile(context) + if err != nil { + log.Fatal(err) + } + pid := context.Int("pid") + if pid <= 0 { + log.Fatal(fmt.Errorf("Invalid pid : %d", pid)) + } + if systemd.UseSystemd() { + _, err := systemd.Apply(config, pid) + if err != nil { + log.Fatal(err) + } + } else { + _, err := fs.Apply(config, pid) + if err != nil { + log.Fatal(err) + } + } +} + +func destroyAction(context *cli.Context) { + config, err := getConfig(context) + if err != nil { + log.Fatal(err) + } + + killAll(config) + // Systemd will clean up cgroup state for empty container. + if !systemd.UseSystemd() { + err := fs.Cleanup(config) + if err != nil { + log.Fatal(err) + } + } +} + +func statsAction(context *cli.Context) { + config, err := getConfig(context) + if err != nil { + log.Fatal(err) + } + stats, err := fs.GetStats(config) + if err != nil { + log.Fatal(err) + } + + out, err := json.MarshalIndent(stats, "", "\t") + if err != nil { + log.Fatal(err) + } + fmt.Printf("Usage stats for '%s':\n %v\n", config.Name, string(out)) +} + +func pauseAction(context *cli.Context) { + setFreezerState(context, cgroups.Frozen) +} + +func resumeAction(context *cli.Context) { + setFreezerState(context, cgroups.Thawed) +} + +func psAction(context *cli.Context) { + config, err := getConfig(context) + if err != nil { + log.Fatal(err) + } + + pids, err := fs.GetPids(config) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Pids in '%s':\n", config.Name) + fmt.Println(pids) +} + +func main() { + logPath := os.Getenv("log") + if logPath != "" { + if err := openLog(logPath); err != nil { + log.Fatal(err) + } + } + + app := cli.NewApp() + app.Name = "cgutil" + app.Usage = "Test utility for libcontainer cgroups package" + app.Version = "0.1" + + app.Commands = []cli.Command{ + createCommand, + destroyCommand, + statsCommand, + pauseCommand, + resumeCommand, + psCommand, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/cgroups/cgutil/sample_cgroup.json b/cgroups/cgutil/sample_cgroup.json new file mode 100644 index 000000000..2d2978494 --- /dev/null +++ b/cgroups/cgutil/sample_cgroup.json @@ -0,0 +1,10 @@ +{ + "name": "luke", + "parent": "darth", + "allow_all_devices": true, + "memory": 1073741824, + "memory_swap": -1, + "cpu_shares": 2048, + "cpu_quota": 500000, + "cpu_period": 250000 +}