fn: size restricted tmpfs /tmp and read-only / support (#1012)

* fn: size restricted tmpfs /tmp and read-only / support

*) read-only Root Fs Support
*) removed CPUShares from docker API. This was unused.
*) docker.Prepare() refactoring
*) added docker.configureTmpFs() for size limited tmpfs on /tmp
*) tmpfs size support in routes and resource tracker
*) fix fn-test-utils to handle sparse files better in create file

* test typo fix
This commit is contained in:
Tolga Ceylan
2018-05-25 14:12:29 -07:00
committed by GitHub
parent 71dbf9fa57
commit 9584643142
15 changed files with 339 additions and 78 deletions

View File

@@ -632,6 +632,113 @@ func TestGetCallReturnsResourceImpossibility(t *testing.T) {
}
}
func TestTmpFsSize(t *testing.T) {
// TODO it may be a good idea to mock out the http server and use a real
// response writer with sync, and also test that this works with async + log
appName := "myapp"
path := "/hello"
url := "http://127.0.0.1:8080/r/" + appName + path
app := &models.App{Name: appName}
app.SetDefaults()
// we need to load in app & route so that FromRequest works
ds := datastore.NewMockInit(
[]*models.App{app},
[]*models.Route{
{
Path: path,
AppID: app.ID,
Image: "fnproject/fn-test-utils",
Type: "sync",
Format: "http", // this _is_ the test
Timeout: 5,
IdleTimeout: 10,
Memory: 64,
TmpFsSize: 1,
},
},
)
cfg, err := NewAgentConfig()
if err != nil {
t.Fatal(err)
}
cfg.MaxTmpFsInodes = 1024
cfg.EnableReadOnlyRootFs = true
a := New(NewDirectDataAccess(ds, ds, new(mqs.Mock)), WithConfig(cfg))
defer checkClose(t, a)
// Here we tell fn-test-utils to read file /proc/mounts and create a /tmp/salsa of 4MB
bodOne := `{"readFile":"/proc/mounts", "createFile":"/tmp/salsa", "createFileSize": 4194304, "isDebug": true}`
req, err := http.NewRequest("GET", url, &dummyReader{Reader: strings.NewReader(bodOne)})
if err != nil {
t.Fatal("unexpected error building request", err)
}
var out bytes.Buffer
callI, err := a.GetCall(FromRequest(a, app, path, req), WithWriter(&out))
if err != nil {
t.Fatal(err)
}
err = a.Submit(callI)
if err != nil {
t.Error("submit should not error:", err)
}
// we're using http format so this will have written a whole http request
res, err := http.ReadResponse(bufio.NewReader(&out), nil)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
// Let's fetch read output and write results. See fn-test-utils AppResponse struct (data field)
var resp struct {
R struct {
MountsRead string `json:"/proc/mounts.read_output"`
CreateFile string `json:"/tmp/salsa.create_error"`
} `json:"data"`
}
json.NewDecoder(res.Body).Decode(&resp)
// Let's check what mounts are on...
mounts := strings.Split(resp.R.MountsRead, "\n")
isFound := false
isRootFound := false
for _, mnt := range mounts {
tokens := strings.Split(mnt, " ")
if len(tokens) < 3 {
continue
}
point := tokens[1]
opts := tokens[3]
if point == "/tmp" && opts == "rw,nosuid,nodev,noexec,relatime,size=1024k,nr_inodes=1024" {
// good
isFound = true
} else if point == "/" && strings.HasPrefix(opts, "ro,") {
// Read-only root, good...
isRootFound = true
}
}
if !isFound || !isRootFound {
t.Fatal(`didn't get proper mounts for /tmp or /, got /proc/mounts content of:\n`, resp.R.MountsRead)
}
// write file should have failed...
if !strings.Contains(resp.R.CreateFile, "no space left on device") {
t.Fatal(`limited tmpfs should generate fs full error, but got output: `, resp.R.CreateFile)
}
}
// return a model with all fields filled in with fnproject/fn-test-utils:latest image, change as needed
func testCall() *models.Call {
appName := "myapp"