Merge pull request #48 from dotcloud/memorylimits

Memorylimits
This commit is contained in:
Solomon Hykes 2013-03-12 12:02:49 -07:00
commit 46dce5918a
7 changed files with 108 additions and 33 deletions

View File

@ -51,12 +51,13 @@ type Container struct {
} }
type Config struct { type Config struct {
Hostname string Hostname string
User string User string
Ram int64 Memory int64 // Memory limit (in bytes)
Ports []int MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
Tty bool // Attach standard streams to a tty, including stdin if it is not closed. Ports []int
OpenStdin bool // Open stdin Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
} }
type NetworkSettings struct { type NetworkSettings struct {

View File

@ -1,9 +1,12 @@
package docker package docker
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/rand"
"os"
"sort" "sort"
"strings" "strings"
"testing" "testing"
@ -21,7 +24,7 @@ func TestStart(t *testing.T) {
[]string{"-al"}, []string{"-al"},
[]string{testLayerPath}, []string{testLayerPath},
&Config{ &Config{
Ram: 33554432, Memory: 33554432,
}, },
) )
if err != nil { if err != nil {
@ -57,7 +60,7 @@ func TestRun(t *testing.T) {
[]string{"-al"}, []string{"-al"},
[]string{testLayerPath}, []string{testLayerPath},
&Config{ &Config{
Ram: 33554432, Memory: 33554432,
}, },
) )
if err != nil { if err != nil {
@ -561,6 +564,58 @@ func TestEnv(t *testing.T) {
} }
} }
func grepFile(t *testing.T, path string, pattern string) {
f, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
r := bufio.NewReader(f)
var (
line string
)
err = nil
for err == nil {
line, err = r.ReadString('\n')
if strings.Contains(line, pattern) == true {
return
}
}
t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path)
}
func TestLXCConfig(t *testing.T) {
docker, err := newTestDocker()
if err != nil {
t.Fatal(err)
}
// Memory is allocated randomly for testing
rand.Seed(time.Now().UTC().UnixNano())
memMin := 33554432
memMax := 536870912
mem := memMin + rand.Intn(memMax-memMin)
container, err := docker.Create(
"config_test",
"/bin/true",
[]string{},
[]string{testLayerPath},
&Config{
Hostname: "foobar",
Memory: int64(mem),
},
)
if err != nil {
t.Fatal(err)
}
defer docker.Destroy(container)
container.generateLXCConfig()
grepFile(t, container.lxcConfigPath, "lxc.utsname = foobar")
grepFile(t, container.lxcConfigPath,
fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem))
grepFile(t, container.lxcConfigPath,
fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2))
}
func BenchmarkRunSequencial(b *testing.B) { func BenchmarkRunSequencial(b *testing.B) {
docker, err := newTestDocker() docker, err := newTestDocker()
if err != nil { if err != nil {

View File

@ -85,16 +85,32 @@ lxc.mount.entry = /etc/resolv.conf {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod net_raw setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod net_raw setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
# limits # limits
{{if .Config.Ram}} {{if .Config.Memory}}
lxc.cgroup.memory.limit_in_bytes = {{.Config.Ram}} lxc.cgroup.memory.limit_in_bytes = {{.Config.Memory}}
lxc.cgroup.memory.soft_limit_in_bytes = {{.Config.Memory}}
{{with $memSwap := getMemorySwap .Config}}
lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}}
{{end}}
{{end}} {{end}}
` `
var LxcTemplateCompiled *template.Template var LxcTemplateCompiled *template.Template
func getMemorySwap(config *Config) int64 {
// By default, MemorySwap is set to twice the size of RAM.
// If you want to omit MemorySwap, set it to `-1'.
if config.MemorySwap < 0 {
return 0
}
return config.Memory * 2
}
func init() { func init() {
var err error var err error
LxcTemplateCompiled, err = template.New("lxc").Parse(LxcTemplate) funcMap := template.FuncMap{
"getMemorySwap": getMemorySwap,
}
LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -1,13 +1,12 @@
package rcli package rcli
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"fmt"
) )
// Use this key to encode an RPC call into an URL, // Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon // eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q" const ARG_URL_KEY = "q"
@ -16,18 +15,16 @@ func URLToCall(u *url.URL) (method string, args []string) {
return path.Base(u.Path), u.Query()[ARG_URL_KEY] return path.Base(u.Path), u.Query()[ARG_URL_KEY]
} }
func ListenAndServeHTTP(addr string, service Service) error { func ListenAndServeHTTP(addr string, service Service) error {
return http.ListenAndServe(addr, http.HandlerFunc( return http.ListenAndServe(addr, http.HandlerFunc(
func (w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
cmd, args := URLToCall(r.URL) cmd, args := URLToCall(r.URL)
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil { if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
fmt.Fprintf(w, "Error: " + err.Error() + "\n") fmt.Fprintf(w, "Error: "+err.Error()+"\n")
} }
})) }))
} }
type AutoFlush struct { type AutoFlush struct {
http.ResponseWriter http.ResponseWriter
} }

View File

@ -1,13 +1,13 @@
package rcli package rcli
import ( import (
"bufio"
"encoding/json"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"log" "log"
"fmt" "net"
"encoding/json"
"bufio"
) )
// Connect to a remote endpoint using protocol `proto` and address `addr`, // Connect to a remote endpoint using protocol `proto` and address `addr`,
@ -44,7 +44,7 @@ func ListenAndServe(proto, addr string, service Service) error {
go func() { go func() {
if err := Serve(conn, service); err != nil { if err := Serve(conn, service); err != nil {
log.Printf("Error: " + err.Error() + "\n") log.Printf("Error: " + err.Error() + "\n")
fmt.Fprintf(conn, "Error: " + err.Error() + "\n") fmt.Fprintf(conn, "Error: "+err.Error()+"\n")
} }
conn.Close() conn.Close()
}() }()
@ -53,7 +53,6 @@ func ListenAndServe(proto, addr string, service Service) error {
return nil return nil
} }
// Parse an rcli call on a new connection, and pass it to `service` if it // Parse an rcli call on a new connection, and pass it to `service` if it
// is valid. // is valid.
func Serve(conn io.ReadWriter, service Service) error { func Serve(conn io.ReadWriter, service Service) error {
@ -68,4 +67,3 @@ func Serve(conn io.ReadWriter, service Service) error {
} }
return nil return nil
} }

View File

@ -8,13 +8,13 @@ package rcli
// are the usual suspects. // are the usual suspects.
import ( import (
"errors"
"flag"
"fmt" "fmt"
"io" "io"
"reflect"
"flag"
"log" "log"
"reflect"
"strings" "strings"
"errors"
) )
type Service interface { type Service interface {
@ -25,7 +25,6 @@ type Service interface {
type Cmd func(io.ReadCloser, io.Writer, ...string) error type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) == 0 { if len(args) == 0 {
args = []string{"help"} args = []string{"help"}
@ -63,7 +62,7 @@ func getMethod(service Service, name string) Cmd {
return nil return nil
} }
} }
methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:]) methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
method, exists := reflect.TypeOf(service).MethodByName(methodName) method, exists := reflect.TypeOf(service).MethodByName(methodName)
if !exists { if !exists {
return nil return nil
@ -91,4 +90,3 @@ func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet
} }
return flags return flags
} }

View File

@ -721,10 +721,18 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
return errors.New("No such container: " + cmd.Arg(0)) return errors.New("No such container: " + cmd.Arg(0))
} }
func (srv *Server) CreateContainer(img *image.Image, ports []int, user string, tty bool, openStdin bool, comment string, cmd string, args ...string) (*docker.Container, error) { func (srv *Server) CreateContainer(img *image.Image, ports []int, user string,
tty bool, openStdin bool, memory int64, comment string, cmd string, args ...string) (*docker.Container, error) {
id := future.RandomId()[:8] id := future.RandomId()[:8]
container, err := srv.containers.Create(id, cmd, args, img.Layers, container, err := srv.containers.Create(id, cmd, args, img.Layers,
&docker.Config{Hostname: id, Ports: ports, User: user, Tty: tty, OpenStdin: openStdin}) &docker.Config{
Hostname: id,
Ports: ports,
User: user,
Tty: tty,
OpenStdin: openStdin,
Memory: memory,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -808,6 +816,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached") fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty") fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty")
fl_comment := cmd.String("c", "", "Comment") fl_comment := cmd.String("c", "", "Comment")
fl_memory := cmd.Int64("m", 0, "Memory limit (in bytes)")
var fl_ports ports var fl_ports ports
cmd.Var(&fl_ports, "p", "Map a network port to the container") cmd.Var(&fl_ports, "p", "Map a network port to the container")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
@ -835,7 +844,8 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
return errors.New("No such image: " + name) return errors.New("No such image: " + name)
} }
// Create new container // Create new container
container, err := srv.CreateContainer(img, fl_ports, *fl_user, *fl_tty, *fl_stdin, *fl_comment, cmdline[0], cmdline[1:]...) container, err := srv.CreateContainer(img, fl_ports, *fl_user, *fl_tty,
*fl_stdin, *fl_memory, *fl_comment, cmdline[0], cmdline[1:]...)
if err != nil { if err != nil {
return errors.New("Error creating container: " + err.Error()) return errors.New("Error creating container: " + err.Error())
} }