github.com/anuvu/nomad@v0.8.7-atom1/client/driver/lxc_test.go (about) 1 //+build linux,lxc 2 3 package driver 4 5 import ( 6 "bytes" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "testing" 13 "time" 14 15 "github.com/hashicorp/nomad/client/config" 16 cstructs "github.com/hashicorp/nomad/client/structs" 17 ctestutil "github.com/hashicorp/nomad/client/testutil" 18 "github.com/hashicorp/nomad/nomad/structs" 19 "github.com/hashicorp/nomad/testutil" 20 lxc "gopkg.in/lxc/go-lxc.v2" 21 ) 22 23 func TestLxcDriver_Fingerprint(t *testing.T) { 24 t.Parallel() 25 if !lxcPresent(t) { 26 t.Skip("lxc not present") 27 } 28 29 task := &structs.Task{ 30 Name: "foo", 31 Driver: "lxc", 32 Resources: structs.DefaultResources(), 33 } 34 35 ctx := testDriverContexts(t, task) 36 defer ctx.AllocDir.Destroy() 37 d := NewLxcDriver(ctx.DriverCtx) 38 39 node := &structs.Node{ 40 Attributes: map[string]string{}, 41 } 42 43 // test with an empty config 44 { 45 request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node} 46 var response cstructs.FingerprintResponse 47 err := d.Fingerprint(request, &response) 48 if err != nil { 49 t.Fatalf("err: %v", err) 50 } 51 } 52 53 // test when lxc is enable din the config 54 { 55 conf := &config.Config{Options: map[string]string{lxcConfigOption: "1"}} 56 request := &cstructs.FingerprintRequest{Config: conf, Node: node} 57 var response cstructs.FingerprintResponse 58 err := d.Fingerprint(request, &response) 59 if err != nil { 60 t.Fatalf("err: %v", err) 61 } 62 63 if !response.Detected { 64 t.Fatalf("expected response to be applicable") 65 } 66 67 if response.Attributes["driver.lxc"] == "" { 68 t.Fatalf("missing driver") 69 } 70 } 71 } 72 73 func TestLxcDriver_Start_Wait(t *testing.T) { 74 if !testutil.IsTravis() { 75 t.Parallel() 76 } 77 if !lxcPresent(t) { 78 t.Skip("lxc not present") 79 } 80 ctestutil.RequireRoot(t) 81 82 task := &structs.Task{ 83 Name: "foo", 84 Driver: "lxc", 85 Config: map[string]interface{}{ 86 "template": "/usr/share/lxc/templates/lxc-busybox", 87 "volumes": []string{"/tmp/:mnt/tmp"}, 88 }, 89 KillTimeout: 10 * time.Second, 90 Resources: structs.DefaultResources(), 91 } 92 93 testFileContents := []byte("this should be visible under /mnt/tmp") 94 tmpFile, err := ioutil.TempFile("/tmp", "testlxcdriver_start_wait") 95 if err != nil { 96 t.Fatalf("error writing temp file: %v", err) 97 } 98 defer os.Remove(tmpFile.Name()) 99 if _, err := tmpFile.Write(testFileContents); err != nil { 100 t.Fatalf("error writing temp file: %v", err) 101 } 102 if err := tmpFile.Close(); err != nil { 103 t.Fatalf("error closing temp file: %v", err) 104 } 105 106 ctx := testDriverContexts(t, task) 107 defer ctx.AllocDir.Destroy() 108 d := NewLxcDriver(ctx.DriverCtx) 109 110 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 111 t.Fatalf("prestart err: %v", err) 112 } 113 sresp, err := d.Start(ctx.ExecCtx, task) 114 if err != nil { 115 t.Fatalf("err: %v", err) 116 } 117 118 lxcHandle, _ := sresp.Handle.(*lxcDriverHandle) 119 120 // Destroy the container after the test 121 defer func() { 122 lxcHandle.container.Stop() 123 lxcHandle.container.Destroy() 124 }() 125 126 testutil.WaitForResult(func() (bool, error) { 127 state := lxcHandle.container.State() 128 if state == lxc.RUNNING { 129 return true, nil 130 } 131 return false, fmt.Errorf("container in state: %v", state) 132 }, func(err error) { 133 t.Fatalf("err: %v", err) 134 }) 135 136 // Look for mounted directories in their proper location 137 containerName := fmt.Sprintf("%s-%s", task.Name, ctx.DriverCtx.allocID) 138 for _, mnt := range []string{"alloc", "local", "secrets", "mnt/tmp"} { 139 fullpath := filepath.Join(lxcHandle.lxcPath, containerName, "rootfs", mnt) 140 stat, err := os.Stat(fullpath) 141 if err != nil { 142 t.Fatalf("err %v", err) 143 } 144 if !stat.IsDir() { 145 t.Fatalf("expected %q to be a dir", fullpath) 146 } 147 } 148 149 // Test that /mnt/tmp/$tempFile exists in the container: 150 mountedContents, err := exec.Command("lxc-attach", "-n", containerName, "--", "cat", filepath.Join("/mnt/", tmpFile.Name())).Output() 151 if err != nil { 152 t.Fatalf("err reading temp file in bind mount: %v", err) 153 } 154 155 if !bytes.Equal(mountedContents, testFileContents) { 156 t.Fatalf("contents of temp bind mounted file did not match, was '%s'", mountedContents) 157 } 158 159 // Destroy the container 160 if err := sresp.Handle.Kill(); err != nil { 161 t.Fatalf("err: %v", err) 162 } 163 164 select { 165 case res := <-sresp.Handle.WaitCh(): 166 if !res.Successful() { 167 t.Fatalf("err: %v", res) 168 } 169 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 170 t.Fatalf("timeout") 171 } 172 } 173 174 func TestLxcDriver_Open_Wait(t *testing.T) { 175 if !testutil.IsTravis() { 176 t.Parallel() 177 } 178 if !lxcPresent(t) { 179 t.Skip("lxc not present") 180 } 181 ctestutil.RequireRoot(t) 182 183 task := &structs.Task{ 184 Name: "foo", 185 Driver: "lxc", 186 Config: map[string]interface{}{ 187 "template": "/usr/share/lxc/templates/lxc-busybox", 188 }, 189 KillTimeout: 10 * time.Second, 190 Resources: structs.DefaultResources(), 191 } 192 193 ctx := testDriverContexts(t, task) 194 defer ctx.AllocDir.Destroy() 195 d := NewLxcDriver(ctx.DriverCtx) 196 197 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 198 t.Fatalf("prestart err: %v", err) 199 } 200 sresp, err := d.Start(ctx.ExecCtx, task) 201 if err != nil { 202 t.Fatalf("err: %v", err) 203 } 204 205 // Destroy the container after the test 206 lh := sresp.Handle.(*lxcDriverHandle) 207 defer func() { 208 lh.container.Stop() 209 lh.container.Destroy() 210 }() 211 212 handle2, err := d.Open(ctx.ExecCtx, lh.ID()) 213 if err != nil { 214 t.Fatalf("err: %v", err) 215 } 216 217 if handle2 == nil { 218 t.Fatalf("missing handle on open") 219 } 220 221 lxcHandle, _ := handle2.(*lxcDriverHandle) 222 223 testutil.WaitForResult(func() (bool, error) { 224 state := lxcHandle.container.State() 225 if state == lxc.RUNNING { 226 return true, nil 227 } 228 return false, fmt.Errorf("container in state: %v", state) 229 }, func(err error) { 230 t.Fatalf("err: %v", err) 231 }) 232 233 // Destroy the container 234 if err := handle2.Kill(); err != nil { 235 t.Fatalf("err: %v", err) 236 } 237 } 238 239 func lxcPresent(t *testing.T) bool { 240 return lxc.Version() != "" 241 } 242 243 func TestLxcDriver_Volumes_ConfigValidation(t *testing.T) { 244 if !testutil.IsTravis() { 245 t.Parallel() 246 } 247 if !lxcPresent(t) { 248 t.Skip("lxc not present") 249 } 250 ctestutil.RequireRoot(t) 251 252 brokenVolumeConfigs := [][]string{ 253 { 254 "foo:/var", 255 }, 256 { 257 ":", 258 }, 259 { 260 "abc:", 261 }, 262 { 263 ":def", 264 }, 265 { 266 "abc:def:ghi", 267 }, 268 } 269 270 for _, bc := range brokenVolumeConfigs { 271 if err := testVolumeConfig(t, bc); err == nil { 272 t.Fatalf("error expected in validate for config %+v", bc) 273 } 274 } 275 if err := testVolumeConfig(t, []string{"abc:def"}); err != nil { 276 t.Fatalf("error in validate for syntactically valid config abc:def was %v", err) 277 } 278 } 279 280 func testVolumeConfig(t *testing.T, volConfig []string) error { 281 task := &structs.Task{ 282 Name: "voltest", 283 Driver: "lxc", 284 KillTimeout: 10 * time.Second, 285 Resources: structs.DefaultResources(), 286 Config: map[string]interface{}{ 287 "template": "busybox", 288 }, 289 } 290 task.Config["volumes"] = volConfig 291 292 ctx := testDriverContexts(t, task) 293 defer ctx.AllocDir.Destroy() 294 295 driver := NewLxcDriver(ctx.DriverCtx) 296 297 err := driver.Validate(task.Config) 298 return err 299 300 } 301 302 func TestLxcDriver_Start_NoVolumes(t *testing.T) { 303 if !testutil.IsTravis() { 304 t.Parallel() 305 } 306 if !lxcPresent(t) { 307 t.Skip("lxc not present") 308 } 309 ctestutil.RequireRoot(t) 310 311 task := &structs.Task{ 312 Name: "foo", 313 Driver: "lxc", 314 Config: map[string]interface{}{ 315 "template": "/usr/share/lxc/templates/lxc-busybox", 316 "volumes": []string{"/tmp/:mnt/tmp"}, 317 }, 318 KillTimeout: 10 * time.Second, 319 Resources: structs.DefaultResources(), 320 } 321 322 ctx := testDriverContexts(t, task) 323 defer ctx.AllocDir.Destroy() 324 325 // set lxcVolumesConfigOption to false to disallow absolute paths as the source for the bind mount 326 ctx.DriverCtx.config.Options = map[string]string{lxcVolumesConfigOption: "false"} 327 328 d := NewLxcDriver(ctx.DriverCtx) 329 330 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 331 t.Fatalf("prestart err: %v", err) 332 } 333 334 // expect the "absolute bind-mount volume in config.. " error 335 _, err := d.Start(ctx.ExecCtx, task) 336 if err == nil { 337 t.Fatalf("expected error in start, got nil.") 338 } 339 340 // Because the container was created but not started before 341 // the expected error, we can test that the destroy-only 342 // cleanup is done here. 343 containerName := fmt.Sprintf("%s-%s", task.Name, ctx.DriverCtx.allocID) 344 if err := exec.Command("bash", "-c", fmt.Sprintf("lxc-ls -1 | grep -q %s", containerName)).Run(); err == nil { 345 t.Fatalf("error, container '%s' is still around", containerName) 346 } 347 348 }