github.com/sameo/containerd@v0.2.8/integration-test/check_test.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"golang.org/x/net/context"
    17  
    18  	"google.golang.org/grpc"
    19  	"google.golang.org/grpc/grpclog"
    20  	"google.golang.org/grpc/health/grpc_health_v1"
    21  
    22  	"github.com/docker/containerd/api/grpc/types"
    23  	utils "github.com/docker/containerd/testutils"
    24  	"github.com/go-check/check"
    25  	"github.com/golang/protobuf/ptypes/timestamp"
    26  )
    27  
    28  func Test(t *testing.T) {
    29  	check.TestingT(t)
    30  }
    31  
    32  func init() {
    33  	check.Suite(&ContainerdSuite{})
    34  }
    35  
    36  type ContainerdSuite struct {
    37  	cwd               string
    38  	outputDir         string
    39  	stateDir          string
    40  	grpcSocket        string
    41  	logFile           *os.File
    42  	cd                *exec.Cmd
    43  	syncChild         chan error
    44  	grpcClient        types.APIClient
    45  	eventFiltersMutex sync.Mutex
    46  	eventFilters      map[string]func(event *types.Event)
    47  	lastEventTs       *timestamp.Timestamp
    48  }
    49  
    50  // getClient returns a connection to the Suite containerd
    51  func (cs *ContainerdSuite) getClient(socket string) error {
    52  	// Parse proto://address form addresses.
    53  	bindParts := strings.SplitN(socket, "://", 2)
    54  	if len(bindParts) != 2 {
    55  		return fmt.Errorf("bad bind address format %s, expected proto://address", socket)
    56  	}
    57  
    58  	// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
    59  	grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
    60  	dialOpts := []grpc.DialOption{grpc.WithInsecure()}
    61  	dialOpts = append(dialOpts,
    62  		grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
    63  			return net.DialTimeout(bindParts[0], bindParts[1], timeout)
    64  		}),
    65  		grpc.WithBlock(),
    66  		grpc.WithTimeout(5*time.Second),
    67  	)
    68  	conn, err := grpc.Dial(socket, dialOpts...)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	healthClient := grpc_health_v1.NewHealthClient(conn)
    73  	if _, err := healthClient.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{}); err != nil {
    74  		return err
    75  	}
    76  	cs.grpcClient = types.NewAPIClient(conn)
    77  
    78  	return nil
    79  }
    80  
    81  // ContainerdEventsHandler will process all events coming from
    82  // containerd. If a filter as been register for a given container id
    83  // via `SetContainerEventFilter()`, it will be invoked every time an
    84  // event for that id is received
    85  func (cs *ContainerdSuite) ContainerdEventsHandler(events types.API_EventsClient) {
    86  	for {
    87  		e, err := events.Recv()
    88  		if err != nil {
    89  			// If daemon died or exited, return
    90  			if strings.Contains(err.Error(), "transport is closing") {
    91  				break
    92  			}
    93  			time.Sleep(1 * time.Second)
    94  			events, _ = cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: cs.lastEventTs})
    95  			continue
    96  		}
    97  		cs.lastEventTs = e.Timestamp
    98  		cs.eventFiltersMutex.Lock()
    99  		if f, ok := cs.eventFilters[e.Id]; ok {
   100  			f(e)
   101  			if e.Type == "exit" && e.Pid == "init" {
   102  				delete(cs.eventFilters, e.Id)
   103  			}
   104  		}
   105  		cs.eventFiltersMutex.Unlock()
   106  	}
   107  }
   108  
   109  func (cs *ContainerdSuite) StopDaemon(kill bool) {
   110  	if cs.cd == nil {
   111  		return
   112  	}
   113  
   114  	if kill {
   115  		cs.cd.Process.Kill()
   116  		<-cs.syncChild
   117  	} else {
   118  		// Terminate gently if possible
   119  		cs.cd.Process.Signal(os.Interrupt)
   120  
   121  		done := false
   122  		for done == false {
   123  			select {
   124  			case err := <-cs.syncChild:
   125  				if err != nil {
   126  					fmt.Printf("master containerd did not exit cleanly: %v\n", err)
   127  				}
   128  				done = true
   129  			case <-time.After(3 * time.Second):
   130  				fmt.Println("Timeout while waiting for containerd to exit, killing it!")
   131  				cs.cd.Process.Kill()
   132  			}
   133  		}
   134  	}
   135  	cs.cd = nil
   136  }
   137  
   138  func (cs *ContainerdSuite) StartDaemon() error {
   139  	cd := exec.Command("containerd", "--debug",
   140  		"--state-dir", cs.stateDir,
   141  		"--listen", cs.grpcSocket,
   142  		"--metrics-interval", "0m0s",
   143  		"--runtime-args", fmt.Sprintf("--root=%s", filepath.Join(cs.cwd, cs.outputDir, "runc")),
   144  	)
   145  	cd.Stderr = cs.logFile
   146  	cd.Stdout = cs.logFile
   147  
   148  	if err := cd.Start(); err != nil {
   149  		return err
   150  	}
   151  	cs.cd = cd
   152  
   153  	if err := cs.getClient(cs.grpcSocket); err != nil {
   154  		// Kill the daemon
   155  		cs.cd.Process.Kill()
   156  		return err
   157  	}
   158  
   159  	// Monitor events
   160  	events, err := cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: cs.lastEventTs})
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	go cs.ContainerdEventsHandler(events)
   166  
   167  	go func() {
   168  		cs.syncChild <- cd.Wait()
   169  	}()
   170  
   171  	return nil
   172  }
   173  
   174  func (cs *ContainerdSuite) RestartDaemon(kill bool) error {
   175  	cs.StopDaemon(kill)
   176  	return cs.StartDaemon()
   177  }
   178  
   179  func (cs *ContainerdSuite) SetUpSuite(c *check.C) {
   180  	bundleMap = make(map[string]Bundle)
   181  	cs.eventFilters = make(map[string]func(event *types.Event))
   182  
   183  	// Get working directory for tests
   184  	wd := utils.GetTestOutDir()
   185  	if err := os.Chdir(wd); err != nil {
   186  		c.Fatalf("Could not change working directory: %v", err)
   187  	}
   188  	cs.cwd = wd
   189  
   190  	// Clean old bundles
   191  	os.RemoveAll(utils.BundlesRoot)
   192  
   193  	// Ensure the oci bundles directory exists
   194  	if err := os.MkdirAll(utils.BundlesRoot, 0755); err != nil {
   195  		c.Fatalf("Failed to create bundles directory: %v", err)
   196  	}
   197  
   198  	// Generate the reference spec
   199  	if err := utils.GenerateReferenceSpecs(utils.BundlesRoot); err != nil {
   200  		c.Fatalf("Unable to generate OCI reference spec: %v", err)
   201  	}
   202  
   203  	// Create our output directory
   204  	cs.outputDir = fmt.Sprintf(utils.OutputDirFormat, time.Now().Format("2006-01-02_150405.000000"))
   205  
   206  	cs.stateDir = filepath.Join(cs.outputDir, "containerd-master")
   207  	if err := os.MkdirAll(cs.stateDir, 0755); err != nil {
   208  		c.Fatalf("Unable to created output directory '%s': %v", cs.stateDir, err)
   209  	}
   210  
   211  	cs.grpcSocket = "unix://" + filepath.Join(cs.outputDir, "containerd-master", "containerd.sock")
   212  	cdLogFile := filepath.Join(cs.outputDir, "containerd-master", "containerd.log")
   213  
   214  	f, err := os.OpenFile(cdLogFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR|os.O_SYNC, 0777)
   215  	if err != nil {
   216  		c.Fatalf("Failed to create master containerd log file: %v", err)
   217  	}
   218  	cs.logFile = f
   219  
   220  	cs.syncChild = make(chan error)
   221  	cs.RestartDaemon(false)
   222  }
   223  
   224  func (cs *ContainerdSuite) TearDownSuite(c *check.C) {
   225  
   226  	// tell containerd to stop
   227  	if cs.cd != nil {
   228  		cs.cd.Process.Signal(os.Interrupt)
   229  
   230  		done := false
   231  		for done == false {
   232  			select {
   233  			case err := <-cs.syncChild:
   234  				if err != nil {
   235  					c.Errorf("master containerd did not exit cleanly: %v", err)
   236  				}
   237  				done = true
   238  			case <-time.After(3 * time.Second):
   239  				fmt.Println("Timeout while waiting for containerd to exit, killing it!")
   240  				cs.cd.Process.Kill()
   241  			}
   242  		}
   243  	}
   244  
   245  	if cs.logFile != nil {
   246  		cs.logFile.Close()
   247  	}
   248  }
   249  
   250  func (cs *ContainerdSuite) SetContainerEventFilter(id string, filter func(event *types.Event)) {
   251  	cs.eventFiltersMutex.Lock()
   252  	cs.eventFilters[id] = filter
   253  	cs.eventFiltersMutex.Unlock()
   254  }
   255  
   256  func (cs *ContainerdSuite) TearDownTest(c *check.C) {
   257  	ctrs, err := cs.ListRunningContainers()
   258  	if err != nil {
   259  		c.Fatalf("Unable to retrieve running containers: %v", err)
   260  	}
   261  
   262  	// Kill all containers that survived
   263  	for _, ctr := range ctrs {
   264  		ch := make(chan interface{})
   265  		cs.SetContainerEventFilter(ctr.Id, func(e *types.Event) {
   266  			if e.Type == "exit" && e.Pid == "init" {
   267  				ch <- nil
   268  			}
   269  		})
   270  
   271  		if err := cs.KillContainer(ctr.Id); err != nil {
   272  			fmt.Fprintf(os.Stderr, "Failed to cleanup leftover test containers: %v\n", err)
   273  		}
   274  
   275  		select {
   276  		case <-ch:
   277  		case <-time.After(3 * time.Second):
   278  			fmt.Fprintf(os.Stderr, "TearDownTest: Containerd %v didn't die after 3 seconds\n", ctr.Id)
   279  		}
   280  	}
   281  }