github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/tests/rkt_image_list_test.go (about)

     1  // Copyright 2015 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // +build host coreos src kvm
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/rkt/rkt/common"
    28  	"github.com/rkt/rkt/pkg/fileutil"
    29  	"github.com/rkt/rkt/tests/testutils"
    30  )
    31  
    32  type ImageID struct {
    33  	path string
    34  	hash string
    35  }
    36  
    37  func (imgID *ImageID) getShortHash(length int) (string, error) {
    38  	if length >= len(imgID.hash) {
    39  		return "", fmt.Errorf("getShortHash: Hash %s is shorter than %d chars", imgID.hash, length)
    40  	}
    41  
    42  	return imgID.hash[:length], nil
    43  }
    44  
    45  // containsConflictingHash returns an ImageID pair if a conflicting short hash is found. The minimum
    46  // hash of 2 chars is used for comparisons.
    47  func (imgID *ImageID) containsConflictingHash(imgIDs []ImageID) (imgIDPair []ImageID, found bool) {
    48  	shortHash, err := imgID.getShortHash(2)
    49  	if err != nil {
    50  		panic(fmt.Sprintf("containsConflictingHash: %s", err))
    51  	}
    52  
    53  	for _, iID := range imgIDs {
    54  		if strings.HasPrefix(iID.hash, shortHash) {
    55  			imgIDPair = []ImageID{*imgID, iID}
    56  			found = true
    57  			break
    58  		}
    59  	}
    60  	return
    61  }
    62  
    63  func TestImageList(t *testing.T) {
    64  	imageName := "coreos.com/rkt/test-image-list-plaintext"
    65  	imageFile := patchTestACI(unreferencedACI, fmt.Sprintf("--name=%s", imageName))
    66  	defer os.Remove(imageFile)
    67  	imageLongHash := "sha512-" + getHashOrPanic(imageFile)[:64]
    68  	imageShortHash := "sha512-" + getHashOrPanic(imageFile)[:32]
    69  	imageTruncatedHash := "sha512-" + getHashOrPanic(imageFile)[:12]
    70  	ctx := testutils.NewRktRunCtx()
    71  	defer ctx.Cleanup()
    72  	fetchCmd := fmt.Sprintf("%s --insecure-options=image fetch %s", ctx.Cmd(), imageFile)
    73  	runRktAndCheckOutput(t, fetchCmd, imageShortHash, false)
    74  
    75  	tests := []struct {
    76  		testName      string
    77  		options       string
    78  		lookupKeyword string
    79  		expect        string
    80  		shouldFail    bool
    81  	}{
    82  		{
    83  			"--no-legend suppress header",
    84  			"--no-legend",
    85  			"ID",
    86  			"",
    87  			true,
    88  		},
    89  		{
    90  			"--fields emits selected fields (truncated hash)",
    91  			"--fields=id",
    92  			"",
    93  			imageTruncatedHash,
    94  			false,
    95  		},
    96  		{
    97  			"--fields does not emit unwanted fields",
    98  			"--no-legend --fields=name",
    99  			"sha",
   100  			"",
   101  			true,
   102  		},
   103  		{
   104  			"--full emits long hash",
   105  			"--fields=id --full",
   106  			"sha",
   107  			imageLongHash,
   108  			false,
   109  		},
   110  		{
   111  			"--format=json suppress header",
   112  			"--format=json",
   113  			"ID",
   114  			"",
   115  			true,
   116  		},
   117  		{
   118  			"--format=json prints a JSON array",
   119  			"--format=json",
   120  			"",
   121  			"[{",
   122  			false,
   123  		},
   124  		{
   125  			"--format=json-pretty introduces proper spacing",
   126  			"--format=json-pretty",
   127  			"",
   128  			`"id": "`,
   129  			false,
   130  		},
   131  	}
   132  	for i, tt := range tests {
   133  		t.Logf("image-list test #%d: %s", i, tt.testName)
   134  		runCmd := fmt.Sprintf(`/bin/sh -c '%s image list %s | grep "%s" || exit 254'`, ctx.Cmd(), tt.options, tt.lookupKeyword)
   135  		runRktAndCheckOutput(t, runCmd, tt.expect, tt.shouldFail)
   136  	}
   137  }
   138  
   139  func TestImageSize(t *testing.T) {
   140  	ctx := testutils.NewRktRunCtx()
   141  	defer ctx.Cleanup()
   142  
   143  	image := patchTestACI("rkt-size.aci", "--no-compression", "--name=size-test")
   144  	defer os.Remove(image)
   145  
   146  	imageHash := "sha512-" + getHashOrPanic(image)[:64]
   147  
   148  	fi, err := os.Stat(image)
   149  	if err != nil {
   150  		t.Fatalf("cannot stat image %q: %v", image, err)
   151  	}
   152  	imageSize := fi.Size()
   153  
   154  	fetchCmd := fmt.Sprintf("%s --insecure-options=image fetch %s", ctx.Cmd(), image)
   155  	spawnAndWaitOrFail(t, fetchCmd, 0)
   156  
   157  	imageListCmd := fmt.Sprintf("%s image list --no-legend --full", ctx.Cmd())
   158  
   159  	// if we don't support overlay fs, we don't render the image on fetch
   160  	if common.SupportsOverlay() != nil {
   161  		// check that the printed size is the same as the actual image size
   162  		expectedStr := fmt.Sprintf("(?s)%s.*%d.*", imageHash, imageSize)
   163  
   164  		runRktAndCheckRegexOutput(t, imageListCmd, expectedStr)
   165  
   166  		// run the image, so rkt renders it in the tree store
   167  		runCmd := fmt.Sprintf("%s --insecure-options=image run %s", ctx.Cmd(), image)
   168  		spawnAndWaitOrFail(t, runCmd, 0)
   169  	}
   170  
   171  	tmpDir := mustTempDir("rkt_image_list_test")
   172  	defer os.RemoveAll(tmpDir)
   173  	imageRenderCmd := fmt.Sprintf("%s image render --overwrite %s %s", ctx.Cmd(), imageHash, tmpDir)
   174  	spawnAndWaitOrFail(t, imageRenderCmd, 0)
   175  	/*
   176  		recreate the tree store directory contents to get an accurate size:
   177  		- hash file
   178  		- image file
   179  		- rendered file
   180  		NOTE: if/when we add new files to the tree store directory, this test
   181  		will fail and will need an update.
   182  	*/
   183  	if err := ioutil.WriteFile(filepath.Join(tmpDir, "hash"), []byte(imageHash), 0600); err != nil {
   184  		t.Fatalf(`error writing "hash" file: %v`, err)
   185  	}
   186  	if err := ioutil.WriteFile(filepath.Join(tmpDir, "image"), []byte(imageHash), 0600); err != nil {
   187  		t.Fatalf(`error writing "image" file: %v`, err)
   188  	}
   189  	if err := ioutil.WriteFile(filepath.Join(tmpDir, "rendered"), []byte{}, 0600); err != nil {
   190  		t.Fatalf(`error writing "rendered" file: %v`, err)
   191  	}
   192  	tsSize, err := fileutil.DirSize(tmpDir)
   193  	if err != nil {
   194  		t.Fatalf("error calculating rendered size: %v", err)
   195  	}
   196  
   197  	// check the size with the rendered image
   198  	expectedStr := fmt.Sprintf("(?s)%s.*%d.*", imageHash, imageSize+tsSize)
   199  	runRktAndCheckRegexOutput(t, imageListCmd, expectedStr)
   200  
   201  	// gc the pod
   202  	gcCmd := fmt.Sprintf("%s gc --grace-period=0s", ctx.Cmd())
   203  	spawnAndWaitOrFail(t, gcCmd, 0)
   204  
   205  	// image gc to remove the tree store
   206  	imageGCCmd := fmt.Sprintf("%s image gc", ctx.Cmd())
   207  	spawnAndWaitOrFail(t, imageGCCmd, 0)
   208  
   209  	// check that the size goes back to the original (only the image size)
   210  	expectedStr = fmt.Sprintf("(?s)%s.*%d.*", imageHash, imageSize)
   211  	runRktAndCheckRegexOutput(t, imageListCmd, expectedStr)
   212  }
   213  
   214  // TestShortHash tests that the short hash generated by the rkt image list
   215  // command is usable by the commands that accept image hashes.
   216  func TestShortHash(t *testing.T) {
   217  	var (
   218  		imageIDs []ImageID
   219  		iter     int
   220  	)
   221  
   222  	// Generate unique images until we get a collision of the first 2 hash chars
   223  	for {
   224  		image := patchTestACI(fmt.Sprintf("rkt-shorthash-%d.aci", iter), fmt.Sprintf("--name=shorthash--%d", iter))
   225  		defer os.Remove(image)
   226  
   227  		imageHash := getHashOrPanic(image)
   228  		imageID := ImageID{image, imageHash}
   229  
   230  		imageIDPair, isMatch := imageID.containsConflictingHash(imageIDs)
   231  		if isMatch {
   232  			imageIDs = imageIDPair
   233  			break
   234  		}
   235  
   236  		imageIDs = append(imageIDs, imageID)
   237  		iter++
   238  	}
   239  	ctx := testutils.NewRktRunCtx()
   240  	defer ctx.Cleanup()
   241  
   242  	// Pull the 2 images with matching first 2 hash chars into cas
   243  	for _, imageID := range imageIDs {
   244  		cmd := fmt.Sprintf("%s --insecure-options=image fetch %s", ctx.Cmd(), imageID.path)
   245  		t.Logf("Fetching %s: %v", imageID.path, cmd)
   246  		spawnAndWaitOrFail(t, cmd, 0)
   247  	}
   248  
   249  	// Get hash from 'rkt image list'
   250  	hash0 := fmt.Sprintf("sha512-%s", imageIDs[0].hash[:12])
   251  	hash1 := fmt.Sprintf("sha512-%s", imageIDs[1].hash[:12])
   252  	for _, hash := range []string{hash0, hash1} {
   253  		imageListCmd := fmt.Sprintf("%s image list --fields=id --no-legend", ctx.Cmd())
   254  		runRktAndCheckOutput(t, imageListCmd, hash, false)
   255  	}
   256  
   257  	tmpDir := mustTempDir("rkt_image_list_test")
   258  	defer os.RemoveAll(tmpDir)
   259  
   260  	// Define tests
   261  	tests := []struct {
   262  		cmd        string
   263  		shouldFail bool
   264  		expect     string
   265  	}{
   266  		// Try invalid ID
   267  		{
   268  			"image cat-manifest sha512-12341234",
   269  			true,
   270  			"no image IDs found",
   271  		},
   272  		// Try using one char hash
   273  		{
   274  			fmt.Sprintf("image cat-manifest %s", hash0[:len("sha512-")+1]),
   275  			true,
   276  			"image ID too short",
   277  		},
   278  		// Try short hash that collides
   279  		{
   280  			fmt.Sprintf("image cat-manifest %s", hash0[:len("sha512-")+2]),
   281  			true,
   282  			"ambiguous image ID",
   283  		},
   284  		// Test that 12-char hash works with image cat-manifest
   285  		{
   286  			fmt.Sprintf("image cat-manifest %s", hash0),
   287  			false,
   288  			"ImageManifest",
   289  		},
   290  		// Test that 12-char hash works with image export
   291  		{
   292  			fmt.Sprintf("image export --overwrite %s %s/export.aci", hash0, tmpDir),
   293  			false,
   294  			"",
   295  		},
   296  		// Test that 12-char hash works with image render
   297  		{
   298  			fmt.Sprintf("image render --overwrite %s %s", hash0, tmpDir),
   299  			false,
   300  			"",
   301  		},
   302  		// Test that 12-char hash works with image extract
   303  		{
   304  			fmt.Sprintf("image extract --overwrite %s %s", hash0, tmpDir),
   305  			false,
   306  			"",
   307  		},
   308  		// Test that 12-char hash works with prepare
   309  		{
   310  			fmt.Sprintf("prepare --debug %s", hash0),
   311  			false,
   312  			"Writing pod manifest",
   313  		},
   314  		// Test that 12-char hash works with image rm
   315  		{
   316  			fmt.Sprintf("image rm %s", hash1),
   317  			false,
   318  			"successfully removed aci",
   319  		},
   320  	}
   321  
   322  	// Run tests
   323  	for i, tt := range tests {
   324  		runCmd := fmt.Sprintf("%s %s", ctx.Cmd(), tt.cmd)
   325  		t.Logf("Running test #%d", i)
   326  		runRktAndCheckOutput(t, runCmd, tt.expect, tt.shouldFail)
   327  	}
   328  }