mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user