github.com/anuvu/nomad@v0.8.7-atom1/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 alloc := mock.Alloc() 41 task := alloc.Job.TaskGroups[0].Tasks[0] 42 taskEnv := env.NewBuilder(mock.Node(), alloc, task, "global").Build() 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 t.Parallel() 64 testutil.ExecCompatible(t) 65 66 execCmd := ExecCommand{Cmd: "/bin/ls", Args: []string{"-F", "/", "/etc/"}} 67 ctx, allocDir := testExecutorContextWithChroot(t) 68 defer allocDir.Destroy() 69 70 execCmd.FSIsolation = true 71 execCmd.ResourceLimits = true 72 execCmd.User = dstructs.DefaultUnprivilegedUser 73 74 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 75 76 if err := executor.SetContext(ctx); err != nil { 77 t.Fatalf("Unexpected error: %v", err) 78 } 79 80 ps, err := executor.LaunchCmd(&execCmd) 81 if err != nil { 82 t.Fatalf("error in launching command: %v", err) 83 } 84 if ps.Pid == 0 { 85 t.Fatalf("expected process to start and have non zero pid") 86 } 87 state, err := executor.Wait() 88 if err != nil { 89 t.Fatalf("error in waiting for command: %v", err) 90 } 91 if state.ExitCode != 0 { 92 t.Errorf("exited with non-zero code: %v", state.ExitCode) 93 } 94 95 // Check if the resource constraints were applied 96 memLimits := filepath.Join(ps.IsolationConfig.CgroupPaths["memory"], "memory.limit_in_bytes") 97 data, err := ioutil.ReadFile(memLimits) 98 if err != nil { 99 t.Fatalf("err: %v", err) 100 } 101 expectedMemLim := strconv.Itoa(ctx.Task.Resources.MemoryMB * 1024 * 1024) 102 actualMemLim := strings.TrimSpace(string(data)) 103 if actualMemLim != expectedMemLim { 104 t.Fatalf("actual mem limit: %v, expected: %v", string(data), expectedMemLim) 105 } 106 107 if err := executor.Exit(); err != nil { 108 t.Fatalf("error: %v", err) 109 } 110 111 // Check if Nomad has actually removed the cgroups 112 if _, err := os.Stat(memLimits); err == nil { 113 t.Fatalf("file %v hasn't been removed", memLimits) 114 } 115 116 expected := `/: 117 alloc/ 118 bin/ 119 dev/ 120 etc/ 121 lib/ 122 lib64/ 123 local/ 124 proc/ 125 secrets/ 126 tmp/ 127 usr/ 128 129 /etc/: 130 ld.so.cache 131 ld.so.conf 132 ld.so.conf.d/` 133 file := filepath.Join(ctx.LogDir, "web.stdout.0") 134 output, err := ioutil.ReadFile(file) 135 if err != nil { 136 t.Fatalf("Couldn't read file %v", file) 137 } 138 139 act := strings.TrimSpace(string(output)) 140 if act != expected { 141 t.Errorf("Command output incorrectly: want %v; got %v", expected, act) 142 } 143 } 144 145 func TestExecutor_ClientCleanup(t *testing.T) { 146 t.Parallel() 147 testutil.ExecCompatible(t) 148 149 ctx, allocDir := testExecutorContextWithChroot(t) 150 ctx.Task.LogConfig.MaxFiles = 1 151 ctx.Task.LogConfig.MaxFileSizeMB = 300 152 defer allocDir.Destroy() 153 154 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 155 156 if err := executor.SetContext(ctx); err != nil { 157 t.Fatalf("Unexpected error") 158 } 159 160 // Need to run a command which will produce continuous output but not 161 // too quickly to ensure executor.Exit() stops the process. 162 execCmd := ExecCommand{Cmd: "/bin/bash", Args: []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}} 163 execCmd.FSIsolation = true 164 execCmd.ResourceLimits = true 165 execCmd.User = "nobody" 166 167 ps, err := executor.LaunchCmd(&execCmd) 168 if err != nil { 169 t.Fatalf("error in launching command: %v", err) 170 } 171 if ps.Pid == 0 { 172 t.Fatalf("expected process to start and have non zero pid") 173 } 174 time.Sleep(500 * time.Millisecond) 175 if err := executor.Exit(); err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 179 file := filepath.Join(ctx.LogDir, "web.stdout.0") 180 finfo, err := os.Stat(file) 181 if err != nil { 182 t.Fatalf("error stating stdout file: %v", err) 183 } 184 if finfo.Size() == 0 { 185 t.Fatal("Nothing in stdout; expected at least one byte.") 186 } 187 time.Sleep(2 * time.Second) 188 finfo1, err := os.Stat(file) 189 if err != nil { 190 t.Fatalf("error stating stdout file: %v", err) 191 } 192 if finfo.Size() != finfo1.Size() { 193 t.Fatalf("Expected size: %v, actual: %v", finfo.Size(), finfo1.Size()) 194 } 195 }