github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/client/driver/executor/executor_linux_test.go (about) 1 package executor 2 3 import ( 4 "io/ioutil" 5 "log" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/hashicorp/nomad/client/allocdir" 14 "github.com/hashicorp/nomad/client/driver/env" 15 dstructs "github.com/hashicorp/nomad/client/driver/structs" 16 cstructs "github.com/hashicorp/nomad/client/structs" 17 "github.com/hashicorp/nomad/client/testutil" 18 "github.com/hashicorp/nomad/nomad/mock" 19 ) 20 21 // testExecutorContextWithChroot returns an ExecutorContext and AllocDir with 22 // chroot. Use testExecutorContext if you don't need a chroot. 23 // 24 // The caller is responsible for calling AllocDir.Destroy() to cleanup. 25 func testExecutorContextWithChroot(t *testing.T) (*ExecutorContext, *allocdir.AllocDir) { 26 chrootEnv := map[string]string{ 27 "/etc/ld.so.cache": "/etc/ld.so.cache", 28 "/etc/ld.so.conf": "/etc/ld.so.conf", 29 "/etc/ld.so.conf.d": "/etc/ld.so.conf.d", 30 "/lib": "/lib", 31 "/lib64": "/lib64", 32 "/usr/lib": "/usr/lib", 33 "/bin/ls": "/bin/ls", 34 "/bin/echo": "/bin/echo", 35 "/bin/bash": "/bin/bash", 36 "/bin/sleep": "/bin/sleep", 37 "/foobar": "/does/not/exist", 38 } 39 40 taskEnv := env.NewTaskEnvironment(mock.Node()) 41 alloc := mock.Alloc() 42 task := alloc.Job.TaskGroups[0].Tasks[0] 43 44 allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(os.TempDir(), alloc.ID)) 45 if err := allocDir.Build(); err != nil { 46 log.Fatalf("AllocDir.Build() failed: %v", err) 47 } 48 if err := allocDir.NewTaskDir(task.Name).Build(false, chrootEnv, cstructs.FSIsolationChroot); err != nil { 49 allocDir.Destroy() 50 log.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err) 51 } 52 td := allocDir.TaskDirs[task.Name] 53 ctx := &ExecutorContext{ 54 TaskEnv: taskEnv, 55 Task: task, 56 TaskDir: td.Dir, 57 LogDir: td.LogDir, 58 } 59 return ctx, allocDir 60 } 61 62 func TestExecutor_IsolationAndConstraints(t *testing.T) { 63 testutil.ExecCompatible(t) 64 65 execCmd := ExecCommand{Cmd: "/bin/ls", Args: []string{"-F", "/", "/etc/"}} 66 ctx, allocDir := testExecutorContextWithChroot(t) 67 defer allocDir.Destroy() 68 69 execCmd.FSIsolation = true 70 execCmd.ResourceLimits = true 71 execCmd.User = dstructs.DefaultUnpriviledgedUser 72 73 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 74 75 if err := executor.SetContext(ctx); err != nil { 76 t.Fatalf("Unexpected error: %v", err) 77 } 78 79 ps, err := executor.LaunchCmd(&execCmd) 80 if err != nil { 81 t.Fatalf("error in launching command: %v", err) 82 } 83 if ps.Pid == 0 { 84 t.Fatalf("expected process to start and have non zero pid") 85 } 86 _, err = executor.Wait() 87 if err != nil { 88 t.Fatalf("error in waiting for command: %v", err) 89 } 90 91 // Check if the resource contraints were applied 92 memLimits := filepath.Join(ps.IsolationConfig.CgroupPaths["memory"], "memory.limit_in_bytes") 93 data, err := ioutil.ReadFile(memLimits) 94 if err != nil { 95 t.Fatalf("err: %v", err) 96 } 97 expectedMemLim := strconv.Itoa(ctx.Task.Resources.MemoryMB * 1024 * 1024) 98 actualMemLim := strings.TrimSpace(string(data)) 99 if actualMemLim != expectedMemLim { 100 t.Fatalf("actual mem limit: %v, expected: %v", string(data), expectedMemLim) 101 } 102 103 if err := executor.Exit(); err != nil { 104 t.Fatalf("error: %v", err) 105 } 106 107 // Check if Nomad has actually removed the cgroups 108 if _, err := os.Stat(memLimits); err == nil { 109 t.Fatalf("file %v hasn't been removed", memLimits) 110 } 111 112 expected := `/: 113 alloc/ 114 bin/ 115 dev/ 116 etc/ 117 lib/ 118 lib64/ 119 local/ 120 proc/ 121 secrets/ 122 tmp/ 123 usr/ 124 125 /etc/: 126 ld.so.cache 127 ld.so.conf 128 ld.so.conf.d/` 129 file := filepath.Join(ctx.LogDir, "web.stdout.0") 130 output, err := ioutil.ReadFile(file) 131 if err != nil { 132 t.Fatalf("Couldn't read file %v", file) 133 } 134 135 act := strings.TrimSpace(string(output)) 136 if act != expected { 137 t.Fatalf("Command output incorrectly: want %v; got %v", expected, act) 138 } 139 } 140 141 func TestExecutor_ClientCleanup(t *testing.T) { 142 testutil.ExecCompatible(t) 143 144 ctx, allocDir := testExecutorContextWithChroot(t) 145 ctx.Task.LogConfig.MaxFiles = 1 146 ctx.Task.LogConfig.MaxFileSizeMB = 300 147 defer allocDir.Destroy() 148 149 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 150 151 if err := executor.SetContext(ctx); err != nil { 152 t.Fatalf("Unexpected error") 153 } 154 155 // Need to run a command which will produce continuous output but not 156 // too quickly to ensure executor.Exit() stops the process. 157 execCmd := ExecCommand{Cmd: "/bin/bash", Args: []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}} 158 execCmd.FSIsolation = true 159 execCmd.ResourceLimits = true 160 execCmd.User = "nobody" 161 162 ps, err := executor.LaunchCmd(&execCmd) 163 if err != nil { 164 t.Fatalf("error in launching command: %v", err) 165 } 166 if ps.Pid == 0 { 167 t.Fatalf("expected process to start and have non zero pid") 168 } 169 time.Sleep(500 * time.Millisecond) 170 if err := executor.Exit(); err != nil { 171 t.Fatalf("err: %v", err) 172 } 173 174 file := filepath.Join(ctx.LogDir, "web.stdout.0") 175 finfo, err := os.Stat(file) 176 if err != nil { 177 t.Fatalf("error stating stdout file: %v", err) 178 } 179 if finfo.Size() == 0 { 180 t.Fatal("Nothing in stdout; expected at least one byte.") 181 } 182 time.Sleep(2 * time.Second) 183 finfo1, err := os.Stat(file) 184 if err != nil { 185 t.Fatalf("error stating stdout file: %v", err) 186 } 187 if finfo.Size() != finfo1.Size() { 188 t.Fatalf("Expected size: %v, actual: %v", finfo.Size(), finfo1.Size()) 189 } 190 }