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 }