github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/client_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package containerd 18 19 import ( 20 "bytes" 21 "context" 22 "flag" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "os/exec" 28 "testing" 29 "time" 30 31 "github.com/containerd/containerd/defaults" 32 "github.com/containerd/containerd/errdefs" 33 "github.com/containerd/containerd/images" 34 "github.com/containerd/containerd/leases" 35 "github.com/containerd/containerd/log" 36 "github.com/containerd/containerd/log/logtest" 37 "github.com/containerd/containerd/namespaces" 38 "github.com/containerd/containerd/pkg/testutil" 39 "github.com/containerd/containerd/platforms" 40 "github.com/containerd/containerd/sys" 41 "github.com/opencontainers/go-digest" 42 "github.com/opencontainers/image-spec/identity" 43 "github.com/sirupsen/logrus" 44 ) 45 46 var ( 47 address string 48 noDaemon bool 49 noCriu bool 50 supportsCriu bool 51 testNamespace = "testing" 52 ctrdStdioFilePath string 53 54 ctrd = &daemon{} 55 ) 56 57 func init() { 58 flag.StringVar(&address, "address", defaultAddress, "The address to the containerd socket for use in the tests") 59 flag.BoolVar(&noDaemon, "no-daemon", false, "Do not start a dedicated daemon for the tests") 60 flag.BoolVar(&noCriu, "no-criu", false, "Do not run the checkpoint tests") 61 } 62 63 func testContext(t testing.TB) (context.Context, context.CancelFunc) { 64 ctx, cancel := context.WithCancel(context.Background()) 65 ctx = namespaces.WithNamespace(ctx, testNamespace) 66 if t != nil { 67 ctx = logtest.WithT(ctx, t) 68 } 69 return ctx, cancel 70 } 71 72 func TestMain(m *testing.M) { 73 flag.Parse() 74 if testing.Short() { 75 os.Exit(m.Run()) 76 } 77 testutil.RequiresRootM() 78 // check if criu is installed on the system 79 _, err := exec.LookPath("criu") 80 supportsCriu = err == nil && !noCriu 81 82 var ( 83 buf = bytes.NewBuffer(nil) 84 ctx, cancel = testContext(nil) 85 ) 86 defer cancel() 87 88 if !noDaemon { 89 sys.ForceRemoveAll(defaultRoot) 90 91 stdioFile, err := ioutil.TempFile("", "") 92 if err != nil { 93 fmt.Fprintf(os.Stderr, "could not create a new stdio temp file: %s\n", err) 94 os.Exit(1) 95 } 96 defer func() { 97 stdioFile.Close() 98 os.Remove(stdioFile.Name()) 99 }() 100 ctrdStdioFilePath = stdioFile.Name() 101 stdioWriter := io.MultiWriter(stdioFile, buf) 102 103 err = ctrd.start("containerd", address, []string{ 104 "--root", defaultRoot, 105 "--state", defaultState, 106 "--log-level", "debug", 107 "--config", createShimDebugConfig(), 108 }, stdioWriter, stdioWriter) 109 if err != nil { 110 fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) 111 os.Exit(1) 112 } 113 } 114 115 waitCtx, waitCancel := context.WithTimeout(ctx, 4*time.Second) 116 client, err := ctrd.waitForStart(waitCtx) 117 waitCancel() 118 if err != nil { 119 ctrd.Kill() 120 ctrd.Wait() 121 fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) 122 os.Exit(1) 123 } 124 125 // print out the version in information 126 version, err := client.Version(ctx) 127 if err != nil { 128 fmt.Fprintf(os.Stderr, "error getting version: %s\n", err) 129 os.Exit(1) 130 } 131 132 // allow comparison with containerd under test 133 log.G(ctx).WithFields(logrus.Fields{ 134 "version": version.Version, 135 "revision": version.Revision, 136 "runtime": os.Getenv("TEST_RUNTIME"), 137 }).Info("running tests against containerd") 138 139 // pull a seed image 140 log.G(ctx).Info("start to pull seed image") 141 if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil { 142 fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) 143 ctrd.Kill() 144 ctrd.Wait() 145 os.Exit(1) 146 } 147 148 if err := client.Close(); err != nil { 149 fmt.Fprintln(os.Stderr, "failed to close client", err) 150 } 151 152 // run the test 153 status := m.Run() 154 155 if !noDaemon { 156 // tear down the daemon and resources created 157 if err := ctrd.Stop(); err != nil { 158 if err := ctrd.Kill(); err != nil { 159 fmt.Fprintln(os.Stderr, "failed to signal containerd", err) 160 } 161 } 162 if err := ctrd.Wait(); err != nil { 163 if _, ok := err.(*exec.ExitError); !ok { 164 fmt.Fprintln(os.Stderr, "failed to wait for containerd", err) 165 } 166 } 167 168 if err := sys.ForceRemoveAll(defaultRoot); err != nil { 169 fmt.Fprintln(os.Stderr, "failed to remove test root dir", err) 170 os.Exit(1) 171 } 172 173 // only print containerd logs if the test failed or tests were run with -v 174 if status != 0 || testing.Verbose() { 175 fmt.Fprintln(os.Stderr, buf.String()) 176 } 177 } 178 os.Exit(status) 179 } 180 181 func newClient(t testing.TB, address string, opts ...ClientOpt) (*Client, error) { 182 if testing.Short() { 183 t.Skip() 184 } 185 if rt := os.Getenv("TEST_RUNTIME"); rt != "" { 186 opts = append(opts, WithDefaultRuntime(rt)) 187 } 188 // testutil.RequiresRoot(t) is not needed here (already called in TestMain) 189 return New(address, opts...) 190 } 191 192 func TestNewClient(t *testing.T) { 193 t.Parallel() 194 195 client, err := newClient(t, address) 196 if err != nil { 197 t.Fatal(err) 198 } 199 if client == nil { 200 t.Fatal("New() returned nil client") 201 } 202 if err := client.Close(); err != nil { 203 t.Errorf("client closed returned error %v", err) 204 } 205 } 206 207 // All the container's tests depends on this, we need it to run first. 208 func TestImagePull(t *testing.T) { 209 client, err := newClient(t, address) 210 if err != nil { 211 t.Fatal(err) 212 } 213 defer client.Close() 214 215 ctx, cancel := testContext(t) 216 defer cancel() 217 _, err = client.Pull(ctx, testImage, WithPlatformMatcher(platforms.Default())) 218 if err != nil { 219 t.Fatal(err) 220 } 221 } 222 223 func TestImagePullWithDiscardContent(t *testing.T) { 224 client, err := newClient(t, address) 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer client.Close() 229 230 ctx, cancel := testContext(t) 231 defer cancel() 232 233 err = client.ImageService().Delete(ctx, testImage, images.SynchronousDelete()) 234 if err != nil { 235 t.Fatal(err) 236 } 237 238 ls := client.LeasesService() 239 l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour)) 240 if err != nil { 241 t.Fatal(err) 242 } 243 ctx = leases.WithLease(ctx, l.ID) 244 img, err := client.Pull(ctx, testImage, 245 WithPlatformMatcher(platforms.Default()), 246 WithPullUnpack, 247 WithChildLabelMap(images.ChildGCLabelsFilterLayers), 248 ) 249 // Synchronously garbage collect contents 250 if errL := ls.Delete(ctx, l, leases.SynchronousDelete); errL != nil { 251 t.Fatal(errL) 252 } 253 if err != nil { 254 t.Fatal(err) 255 } 256 257 // Check if all layer contents have been unpacked and aren't preserved 258 var ( 259 diffIDs []digest.Digest 260 layers []digest.Digest 261 ) 262 cs := client.ContentStore() 263 manifest, err := images.Manifest(ctx, cs, img.Target(), platforms.Default()) 264 if err != nil { 265 t.Fatal(err) 266 } 267 if len(manifest.Layers) == 0 { 268 t.Fatalf("failed to get children from %v", img.Target()) 269 } 270 for _, l := range manifest.Layers { 271 layers = append(layers, l.Digest) 272 } 273 config, err := images.Config(ctx, cs, img.Target(), platforms.Default()) 274 if err != nil { 275 t.Fatal(err) 276 } 277 diffIDs, err = images.RootFS(ctx, cs, config) 278 if err != nil { 279 t.Fatal(err) 280 } 281 if len(layers) != len(diffIDs) { 282 t.Fatalf("number of layers and diffIDs don't match: %d != %d", len(layers), len(diffIDs)) 283 } else if len(layers) == 0 { 284 t.Fatalf("there is no layers in the target image(parent: %v)", img.Target()) 285 } 286 var ( 287 sn = client.SnapshotService("") 288 chain []digest.Digest 289 ) 290 for i, dgst := range layers { 291 chain = append(chain, diffIDs[i]) 292 chainID := identity.ChainID(chain).String() 293 if _, err := sn.Stat(ctx, chainID); err != nil { 294 t.Errorf("snapshot %v must exist: %v", chainID, err) 295 } 296 if _, err := cs.Info(ctx, dgst); err == nil || !errdefs.IsNotFound(err) { 297 t.Errorf("content %v must be garbage collected: %v", dgst, err) 298 } 299 } 300 } 301 302 func TestImagePullAllPlatforms(t *testing.T) { 303 client, err := newClient(t, address) 304 if err != nil { 305 t.Fatal(err) 306 } 307 defer client.Close() 308 ctx, cancel := testContext(t) 309 defer cancel() 310 311 cs := client.ContentStore() 312 img, err := client.Fetch(ctx, "docker.io/library/busybox:latest") 313 if err != nil { 314 t.Fatal(err) 315 } 316 index := img.Target 317 manifests, err := images.Children(ctx, cs, index) 318 if err != nil { 319 t.Fatal(err) 320 } 321 for _, manifest := range manifests { 322 children, err := images.Children(ctx, cs, manifest) 323 if err != nil { 324 t.Fatal("Th") 325 } 326 // check if childless data type has blob in content store 327 for _, desc := range children { 328 ra, err := cs.ReaderAt(ctx, desc) 329 if err != nil { 330 t.Fatal(err) 331 } 332 ra.Close() 333 } 334 } 335 } 336 337 func TestImagePullSomePlatforms(t *testing.T) { 338 client, err := newClient(t, address) 339 if err != nil { 340 t.Fatal(err) 341 } 342 defer client.Close() 343 ctx, cancel := testContext(t) 344 defer cancel() 345 346 cs := client.ContentStore() 347 platformList := []string{"linux/amd64", "linux/arm64/v8", "linux/s390x"} 348 m := make(map[string]platforms.Matcher) 349 var opts []RemoteOpt 350 351 for _, platform := range platformList { 352 p, err := platforms.Parse(platform) 353 if err != nil { 354 t.Fatal(err) 355 } 356 m[platform] = platforms.NewMatcher(p) 357 opts = append(opts, WithPlatform(platform)) 358 } 359 360 img, err := client.Fetch(ctx, "k8s.gcr.io/pause:3.1", opts...) 361 if err != nil { 362 t.Fatal(err) 363 } 364 365 index := img.Target 366 manifests, err := images.Children(ctx, cs, index) 367 if err != nil { 368 t.Fatal(err) 369 } 370 371 count := 0 372 for _, manifest := range manifests { 373 children, err := images.Children(ctx, cs, manifest) 374 375 found := false 376 for _, matcher := range m { 377 if manifest.Platform == nil { 378 t.Fatal("manifest should have proper platform") 379 } 380 if matcher.Match(*manifest.Platform) { 381 count++ 382 found = true 383 } 384 } 385 386 if found { 387 if len(children) == 0 { 388 t.Fatal("manifest should have pulled children content") 389 } 390 391 // check if childless data type has blob in content store 392 for _, desc := range children { 393 ra, err := cs.ReaderAt(ctx, desc) 394 if err != nil { 395 t.Fatal(err) 396 } 397 ra.Close() 398 } 399 } else if err == nil { 400 t.Fatal("manifest should not have pulled children content") 401 } 402 } 403 404 if count != len(platformList) { 405 t.Fatal("expected a different number of pulled manifests") 406 } 407 } 408 409 func TestImagePullSchema1(t *testing.T) { 410 client, err := newClient(t, address) 411 if err != nil { 412 t.Fatal(err) 413 } 414 defer client.Close() 415 416 ctx, cancel := testContext(t) 417 defer cancel() 418 schema1TestImage := "gcr.io/google_containers/pause:3.0@sha256:0d093c962a6c2dd8bb8727b661e2b5f13e9df884af9945b4cc7088d9350cd3ee" 419 _, err = client.Pull(ctx, schema1TestImage, WithPlatform(platforms.DefaultString()), WithSchema1Conversion) 420 if err != nil { 421 t.Fatal(err) 422 } 423 } 424 425 func TestImagePullWithConcurrencyLimit(t *testing.T) { 426 client, err := newClient(t, address) 427 if err != nil { 428 t.Fatal(err) 429 } 430 defer client.Close() 431 432 ctx, cancel := testContext(t) 433 defer cancel() 434 _, err = client.Pull(ctx, testImage, 435 WithPlatformMatcher(platforms.Default()), 436 WithMaxConcurrentDownloads(2)) 437 if err != nil { 438 t.Fatal(err) 439 } 440 } 441 442 func TestClientReconnect(t *testing.T) { 443 t.Parallel() 444 445 ctx, cancel := testContext(t) 446 defer cancel() 447 448 client, err := newClient(t, address) 449 if err != nil { 450 t.Fatal(err) 451 } 452 if client == nil { 453 t.Fatal("New() returned nil client") 454 } 455 ok, err := client.IsServing(ctx) 456 if err != nil { 457 t.Fatal(err) 458 } 459 if !ok { 460 t.Fatal("containerd is not serving") 461 } 462 if err := client.Reconnect(); err != nil { 463 t.Fatal(err) 464 } 465 if ok, err = client.IsServing(ctx); err != nil { 466 t.Fatal(err) 467 } 468 if !ok { 469 t.Fatal("containerd is not serving") 470 } 471 if err := client.Close(); err != nil { 472 t.Errorf("client closed returned error %v", err) 473 } 474 } 475 476 func createShimDebugConfig() string { 477 f, err := ioutil.TempFile("", "containerd-config-") 478 if err != nil { 479 fmt.Fprintf(os.Stderr, "Failed to create config file: %s\n", err) 480 os.Exit(1) 481 } 482 defer f.Close() 483 if _, err := f.WriteString("version = 1\n"); err != nil { 484 fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err) 485 os.Exit(1) 486 } 487 488 if _, err := f.WriteString("[plugins.linux]\n\tshim_debug = true\n"); err != nil { 489 fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err) 490 os.Exit(1) 491 } 492 493 return f.Name() 494 } 495 496 func TestDefaultRuntimeWithNamespaceLabels(t *testing.T) { 497 client, err := newClient(t, address) 498 if err != nil { 499 t.Fatal(err) 500 } 501 defer client.Close() 502 503 ctx, cancel := testContext(t) 504 defer cancel() 505 namespaces := client.NamespaceService() 506 testRuntime := "testRuntime" 507 runtimeLabel := defaults.DefaultRuntimeNSLabel 508 if err := namespaces.SetLabel(ctx, testNamespace, runtimeLabel, testRuntime); err != nil { 509 t.Fatal(err) 510 } 511 512 testClient, err := New(address, WithDefaultNamespace(testNamespace)) 513 if err != nil { 514 t.Fatal(err) 515 } 516 defer testClient.Close() 517 if testClient.runtime != testRuntime { 518 t.Error("failed to set default runtime from namespace labels") 519 } 520 }