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(&register.Test{
    38  		Run:         podmanBaseTest,
    39  		ClusterSize: 1,
    40  		Name:        `podman.base`,
    41  		Distros:     []string{"rhcos"},
    42  	})
    43  	register.Register(&register.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(&register.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  }