github.com/docker/containerd@v0.2.9-0.20170509230648-8ef7df579710/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/containerd/containerd/api/grpc/types" 23 utils "github.com/containerd/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 }