Przeglądaj źródła

Merge pull request #48 from dotcloud/memorylimits

Memorylimits
Solomon Hykes 12 lat temu
rodzic
commit
46dce5918a
7 zmienionych plików z 108 dodań i 33 usunięć
  1. 7 6
      container.go
  2. 57 2
      container_test.go
  3. 19 3
      lxc_template.go
  4. 3 6
      rcli/http.go
  5. 5 7
      rcli/tcp.go
  6. 4 6
      rcli/types.go
  7. 13 3
      server/server.go

+ 7 - 6
container.go

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

+ 57 - 2
container_test.go

@@ -1,9 +1,12 @@
 package docker
 
 import (
+	"bufio"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math/rand"
+	"os"
 	"sort"
 	"strings"
 	"testing"
@@ -21,7 +24,7 @@ func TestStart(t *testing.T) {
 		[]string{"-al"},
 		[]string{testLayerPath},
 		&Config{
-			Ram: 33554432,
+			Memory: 33554432,
 		},
 	)
 	if err != nil {
@@ -57,7 +60,7 @@ func TestRun(t *testing.T) {
 		[]string{"-al"},
 		[]string{testLayerPath},
 		&Config{
-			Ram: 33554432,
+			Memory: 33554432,
 		},
 	)
 	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) {
 	docker, err := newTestDocker()
 	if err != nil {

+ 19 - 3
lxc_template.go

@@ -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
 
 # limits
-{{if .Config.Ram}}
-lxc.cgroup.memory.limit_in_bytes = {{.Config.Ram}}
+{{if .Config.Memory}}
+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}}
 `
 
 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() {
 	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 {
 		panic(err)
 	}

+ 3 - 6
rcli/http.go

@@ -1,13 +1,12 @@
 package rcli
 
 import (
+	"fmt"
 	"net/http"
 	"net/url"
 	"path"
-	"fmt"
 )
 
-
 // Use this key to encode an RPC call into an URL,
 // eg. domain.tld/path/to/method?q=get_user&q=gordon
 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]
 }
 
-
 func ListenAndServeHTTP(addr string, service Service) error {
 	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)
 			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 {
 	http.ResponseWriter
 }

+ 5 - 7
rcli/tcp.go

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

+ 4 - 6
rcli/types.go

@@ -8,13 +8,13 @@ package rcli
 // are the usual suspects.
 
 import (
+	"errors"
+	"flag"
 	"fmt"
 	"io"
-	"reflect"
-	"flag"
 	"log"
+	"reflect"
 	"strings"
-	"errors"
 )
 
 type Service interface {
@@ -25,7 +25,6 @@ type Service interface {
 type Cmd func(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 {
 	if len(args) == 0 {
 		args = []string{"help"}
@@ -63,7 +62,7 @@ func getMethod(service Service, name string) Cmd {
 			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)
 	if !exists {
 		return nil
@@ -91,4 +90,3 @@ func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet
 	}
 	return flags
 }
-

+ 13 - 3
server/server.go

@@ -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))
 }
 
-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]
 	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 {
 		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_tty := cmd.Bool("t", false, "Allocate a pseudo-tty")
 	fl_comment := cmd.String("c", "", "Comment")
+	fl_memory := cmd.Int64("m", 0, "Memory limit (in bytes)")
 	var fl_ports ports
 	cmd.Var(&fl_ports, "p", "Map a network port to the container")
 	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)
 	}
 	// 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 {
 		return errors.New("Error creating container: " + err.Error())
 	}