github.com/coreos/mantle@v0.13.0/kola/tests/podman/podman.go (about) 1 // Copyright 2018 Red Hat, Inc. 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 package podman 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "strings" 22 "time" 23 24 "golang.org/x/crypto/ssh" 25 "golang.org/x/net/context" 26 27 "github.com/coreos/mantle/kola/cluster" 28 "github.com/coreos/mantle/kola/register" 29 tutil "github.com/coreos/mantle/kola/tests/util" 30 "github.com/coreos/mantle/lang/worker" 31 "github.com/coreos/mantle/platform" 32 "github.com/coreos/mantle/util" 33 ) 34 35 // init runs when the package is imported and takes care of registering tests 36 func init() { 37 register.Register(®ister.Test{ 38 Run: podmanBaseTest, 39 ClusterSize: 1, 40 Name: `podman.base`, 41 Distros: []string{"rhcos"}, 42 }) 43 register.Register(®ister.Test{ 44 Run: podmanWorkflow, 45 ClusterSize: 1, 46 Name: `podman.workflow`, 47 Flags: []register.Flag{register.RequiresInternetAccess}, // For pulling nginx 48 Distros: []string{"rhcos"}, 49 FailFast: true, 50 }) 51 register.Register(®ister.Test{ 52 Run: podmanNetworkTest, 53 ClusterSize: 2, 54 Name: `podman.network`, 55 Distros: []string{"rhcos"}, 56 }) 57 } 58 59 // simplifiedContainerPsInfo represents a container entry in podman ps -a 60 type simplifiedContainerPsInfo struct { 61 ID string `json:"id"` 62 Image string `json:"image"` 63 Status string `json:"status"` 64 } 65 66 // simplifiedPsInfo represents the results of podman ps -a 67 type simplifiedPsInfo struct { 68 containers []simplifiedContainerPsInfo 69 } 70 71 // simplifiedPodmanInfo represents the results of podman info 72 type simplifiedPodmanInfo struct { 73 Store struct { 74 GraphDriverName string `json:"GraphDriverName"` 75 GraphRoot string `json:"GraphRoot"` 76 } 77 } 78 79 func getSimplifiedPsInfo(c cluster.TestCluster, m platform.Machine) (simplifiedPsInfo, error) { 80 target := simplifiedPsInfo{} 81 psJSON, err := c.SSH(m, `sudo podman ps -a --format json`) 82 83 if err != nil { 84 return target, fmt.Errorf("could not get info: %v", err) 85 } 86 87 err = json.Unmarshal(psJSON, &target.containers) 88 89 if err != nil { 90 return target, fmt.Errorf("could not unmarshal info %q into known json: %v", string(psJSON), err) 91 } 92 return target, nil 93 } 94 95 // Returns the result of podman info as a simplifiedPodmanInfo 96 func getPodmanInfo(c cluster.TestCluster, m platform.Machine) (simplifiedPodmanInfo, error) { 97 target := simplifiedPodmanInfo{} 98 99 pInfoJSON, err := c.SSH(m, `sudo podman info --format json`) 100 if err != nil { 101 return target, fmt.Errorf("Could not get info: %v", err) 102 } 103 104 err = json.Unmarshal(pInfoJSON, &target) 105 if err != nil { 106 return target, fmt.Errorf("Could not unmarshal info %q into known JSON: %v", string(pInfoJSON), err) 107 } 108 return target, nil 109 } 110 111 func podmanBaseTest(c cluster.TestCluster) { 112 c.Run("info", podmanInfo) 113 c.Run("resources", podmanResources) 114 c.Run("network", podmanNetworksReliably) 115 } 116 117 // Test: Run basic podman commands 118 func podmanWorkflow(c cluster.TestCluster) { 119 m := c.Machines()[0] 120 121 // Test: Verify container can run with volume mount and port forwarding 122 image := "docker.io/library/nginx" 123 wwwRoot := "/usr/share/nginx/html" 124 var id string 125 126 c.Run("run", func(c cluster.TestCluster) { 127 dir := c.MustSSH(m, `mktemp -d`) 128 cmd := fmt.Sprintf("echo TEST PAGE > %s/index.html", string(dir)) 129 c.MustSSH(m, cmd) 130 131 cmd = fmt.Sprintf("sudo podman run -d -p 80:80 -v %s/index.html:%s/index.html:z %s", string(dir), wwwRoot, image) 132 out := c.MustSSH(m, cmd) 133 id = string(out)[0:12] 134 135 podIsRunning := func() error { 136 b, err := c.SSH(m, `curl -f http://localhost 2>/dev/null`) 137 if err != nil { 138 return err 139 } 140 if !bytes.Contains(b, []byte("TEST PAGE")) { 141 return fmt.Errorf("nginx pod is not running %s", b) 142 } 143 return nil 144 } 145 146 if err := util.Retry(6, 5*time.Second, podIsRunning); err != nil { 147 c.Fatal("Pod is not running") 148 } 149 }) 150 151 // Test: Execute command in container 152 c.Run("exec", func(c cluster.TestCluster) { 153 cmd := fmt.Sprintf("sudo podman exec %s echo hello", id) 154 out := c.MustSSH(m, cmd) 155 156 if string(out) != "hello" { 157 c.Fatal("Could not exec command in container") 158 } 159 }) 160 161 // Test: Stop container 162 c.Run("stop", func(c cluster.TestCluster) { 163 cmd := fmt.Sprintf("sudo podman stop %s", id) 164 c.MustSSH(m, cmd) 165 psInfo, err := getSimplifiedPsInfo(c, m) 166 if err != nil { 167 c.Fatal(err) 168 } 169 170 found := false 171 for _, container := range psInfo.containers { 172 if container.ID == id { 173 found = true 174 if !strings.Contains(strings.ToLower(container.Status), "exited") { 175 c.Fatalf("Container %s was not stopped. Current status: %s", id, container.Status) 176 } 177 } 178 } 179 180 if found == false { 181 c.Fatalf("Unable to find container %s in podman ps -a output", id) 182 } 183 }) 184 185 // Test: Remove container 186 c.Run("remove", func(c cluster.TestCluster) { 187 cmd := fmt.Sprintf("sudo podman rm %s", id) 188 c.MustSSH(m, cmd) 189 psInfo, err := getSimplifiedPsInfo(c, m) 190 if err != nil { 191 c.Fatal(err) 192 } 193 194 found := false 195 for _, container := range psInfo.containers { 196 if container.ID == id { 197 found = true 198 } 199 } 200 201 if found == true { 202 c.Fatalf("Container %s should be removed. %v", id, psInfo.containers) 203 } 204 }) 205 206 // Test: Delete container 207 c.Run("delete", func(c cluster.TestCluster) { 208 cmd := fmt.Sprintf("sudo podman rmi %s", image) 209 out := c.MustSSH(m, cmd) 210 imageID := string(out) 211 212 cmd = fmt.Sprintf("sudo podman images | grep %s", imageID) 213 out, err := c.SSH(m, cmd) 214 if err == nil { 215 c.Fatalf("Image should be deleted but found %s", string(out)) 216 } 217 }) 218 } 219 220 // Test: Verify basic podman info information 221 func podmanInfo(c cluster.TestCluster) { 222 m := c.Machines()[0] 223 info, err := getPodmanInfo(c, m) 224 if err != nil { 225 c.Fatal(err) 226 } 227 228 // test for known settings 229 expectedGraphDriver := "overlay" 230 if info.Store.GraphDriverName != expectedGraphDriver { 231 c.Errorf("Unexpected driver: %v != %v", expectedGraphDriver, info.Store.GraphDriverName) 232 } 233 expectedGraphRoot := "/var/lib/containers/storage" 234 if info.Store.GraphRoot != expectedGraphRoot { 235 c.Errorf("Unexected graph root: %v != %v", expectedGraphRoot, info.Store.GraphRoot) 236 } 237 } 238 239 // Test: Run podman with various options 240 func podmanResources(c cluster.TestCluster) { 241 m := c.Machines()[0] 242 243 tutil.GenPodmanScratchContainer(c, m, "echo", []string{"echo"}) 244 245 podmanFmt := "sudo podman run --rm %s echo echo 1" 246 247 pCmd := func(arg string) string { 248 return fmt.Sprintf(podmanFmt, arg) 249 } 250 251 for _, podmanCmd := range []string{ 252 // must set memory when setting memory-swap 253 pCmd("--memory=10m --memory-swap=10m"), 254 pCmd("--memory-reservation=10m"), 255 pCmd("--kernel-memory=10m"), 256 pCmd("--cpu-shares=100"), 257 pCmd("--cpu-period=1000"), 258 pCmd("--cpuset-cpus=0"), 259 pCmd("--cpuset-mems=0"), 260 pCmd("--cpu-quota=1000"), 261 pCmd("--blkio-weight=10"), 262 pCmd("--memory=10m --oom-kill-disable=true"), 263 pCmd("--memory-swappiness=50"), 264 pCmd("--shm-size=1m"), 265 } { 266 cmd := podmanCmd 267 output, err := c.SSH(m, cmd) 268 if err != nil { 269 c.Fatalf("Failed to run %q: output: %q status: %q", cmd, output, err) 270 } 271 } 272 } 273 274 // Test: Verify network connectivity from containers on two different machines 275 func podmanNetworkTest(c cluster.TestCluster) { 276 machines := c.Machines() 277 src, dest := machines[0], machines[1] 278 279 c.Log("creating ncat containers") 280 281 tutil.GenPodmanScratchContainer(c, src, "ncat", []string{"ncat"}) 282 tutil.GenPodmanScratchContainer(c, dest, "ncat", []string{"ncat"}) 283 284 listener := func(ctx context.Context) error { 285 // Will block until a message is recieved 286 out, err := c.SSH(dest, 287 `echo "HELLO FROM SERVER" | sudo podman run -i -p 9988:9988 ncat ncat --idle-timeout 20 --listen 0.0.0.0 9988`, 288 ) 289 if err != nil { 290 return err 291 } 292 293 if !bytes.Equal(out, []byte("HELLO FROM CLIENT")) { 294 return fmt.Errorf("unexpected result from listener: %q", out) 295 } 296 297 return nil 298 } 299 300 talker := func(ctx context.Context) error { 301 // Wait until listener is ready before trying anything 302 for { 303 _, err := c.SSH(dest, "sudo netstat -tulpn | grep 9988") 304 if err == nil { 305 break // socket is ready 306 } 307 308 exit, ok := err.(*ssh.ExitError) 309 if !ok || exit.Waitmsg.ExitStatus() != 1 { // 1 is the expected exit of grep -q 310 return err 311 } 312 313 select { 314 case <-ctx.Done(): 315 return fmt.Errorf("timeout waiting for server") 316 default: 317 time.Sleep(100 * time.Millisecond) 318 } 319 } 320 321 srcCmd := fmt.Sprintf(`echo "HELLO FROM CLIENT" | sudo podman run -i ncat ncat %s 9988`, dest.PrivateIP()) 322 out, err := c.SSH(src, srcCmd) 323 if err != nil { 324 return err 325 } 326 327 if !bytes.Equal(out, []byte("HELLO FROM SERVER")) { 328 return fmt.Errorf(`unexpected result from listener: "%v"`, out) 329 } 330 331 return nil 332 } 333 334 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 335 defer cancel() 336 337 if err := worker.Parallel(ctx, listener, talker); err != nil { 338 c.Fatal(err) 339 } 340 } 341 342 // Test: Verify basic container network connectivity 343 func podmanNetworksReliably(c cluster.TestCluster) { 344 m := c.Machines()[0] 345 346 tutil.GenPodmanScratchContainer(c, m, "ping", []string{"sh", "ping"}) 347 348 output := c.MustSSH(m, `for i in $(seq 1 100); do 349 echo -n "$i: " 350 sudo podman run --rm ping sh -c 'ping -i 0.2 10.88.0.1 -w 1 >/dev/null && echo PASS || echo FAIL' 351 done`) 352 353 numPass := strings.Count(string(output), "PASS") 354 355 if numPass != 100 { 356 c.Fatalf("Expected 100 passes, but output was: %s", output) 357 } 358 }