github.com/ijc/containerd@v0.2.5/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 cs.cd = nil 118 } else { 119 // Terminate gently if possible 120 cs.cd.Process.Signal(os.Interrupt) 121 122 done := false 123 for done == false { 124 select { 125 case err := <-cs.syncChild: 126 if err != nil { 127 fmt.Printf("master containerd did not exit cleanly: %v\n", err) 128 } 129 done = true 130 case <-time.After(3 * time.Second): 131 fmt.Println("Timeout while waiting for containerd to exit, killing it!") 132 cs.cd.Process.Kill() 133 } 134 } 135 } 136 } 137 138 func (cs *ContainerdSuite) RestartDaemon(kill bool) error { 139 cs.StopDaemon(kill) 140 141 cd := exec.Command("containerd", "--debug", 142 "--state-dir", cs.stateDir, 143 "--listen", cs.grpcSocket, 144 "--metrics-interval", "0m0s", 145 "--runtime-args", fmt.Sprintf("--root=%s", filepath.Join(cs.cwd, cs.outputDir, "runc")), 146 ) 147 cd.Stderr = cs.logFile 148 cd.Stdout = cs.logFile 149 150 if err := cd.Start(); err != nil { 151 return err 152 } 153 cs.cd = cd 154 155 if err := cs.getClient(cs.grpcSocket); err != nil { 156 // Kill the daemon 157 cs.cd.Process.Kill() 158 return err 159 } 160 161 // Monitor events 162 events, err := cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: cs.lastEventTs}) 163 if err != nil { 164 return err 165 } 166 167 go cs.ContainerdEventsHandler(events) 168 169 go func() { 170 cs.syncChild <- cd.Wait() 171 }() 172 173 return nil 174 } 175 176 func (cs *ContainerdSuite) SetUpSuite(c *check.C) { 177 bundleMap = make(map[string]Bundle) 178 cs.eventFilters = make(map[string]func(event *types.Event)) 179 180 // Get working directory for tests 181 wd := utils.GetTestOutDir() 182 if err := os.Chdir(wd); err != nil { 183 c.Fatalf("Could not change working directory: %v", err) 184 } 185 cs.cwd = wd 186 187 // Clean old bundles 188 os.RemoveAll(utils.BundlesRoot) 189 190 // Ensure the oci bundles directory exists 191 if err := os.MkdirAll(utils.BundlesRoot, 0755); err != nil { 192 c.Fatalf("Failed to create bundles directory: %v", err) 193 } 194 195 // Generate the reference spec 196 if err := utils.GenerateReferenceSpecs(utils.BundlesRoot); err != nil { 197 c.Fatalf("Unable to generate OCI reference spec: %v", err) 198 } 199 200 // Create our output directory 201 cs.outputDir = fmt.Sprintf(utils.OutputDirFormat, time.Now().Format("2006-01-02_150405.000000")) 202 203 cs.stateDir = filepath.Join(cs.outputDir, "containerd-master") 204 if err := os.MkdirAll(cs.stateDir, 0755); err != nil { 205 c.Fatalf("Unable to created output directory '%s': %v", cs.stateDir, err) 206 } 207 208 cs.grpcSocket = "unix://" + filepath.Join(cs.outputDir, "containerd-master", "containerd.sock") 209 cdLogFile := filepath.Join(cs.outputDir, "containerd-master", "containerd.log") 210 211 f, err := os.OpenFile(cdLogFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR|os.O_SYNC, 0777) 212 if err != nil { 213 c.Fatalf("Failed to create master containerd log file: %v", err) 214 } 215 cs.logFile = f 216 217 cs.syncChild = make(chan error) 218 cs.RestartDaemon(false) 219 } 220 221 func (cs *ContainerdSuite) TearDownSuite(c *check.C) { 222 223 // tell containerd to stop 224 if cs.cd != nil { 225 cs.cd.Process.Signal(os.Interrupt) 226 227 done := false 228 for done == false { 229 select { 230 case err := <-cs.syncChild: 231 if err != nil { 232 c.Errorf("master containerd did not exit cleanly: %v", err) 233 } 234 done = true 235 case <-time.After(3 * time.Second): 236 fmt.Println("Timeout while waiting for containerd to exit, killing it!") 237 cs.cd.Process.Kill() 238 } 239 } 240 } 241 242 if cs.logFile != nil { 243 cs.logFile.Close() 244 } 245 } 246 247 func (cs *ContainerdSuite) SetContainerEventFilter(id string, filter func(event *types.Event)) { 248 cs.eventFiltersMutex.Lock() 249 cs.eventFilters[id] = filter 250 cs.eventFiltersMutex.Unlock() 251 } 252 253 func (cs *ContainerdSuite) TearDownTest(c *check.C) { 254 ctrs, err := cs.ListRunningContainers() 255 if err != nil { 256 c.Fatalf("Unable to retrieve running containers: %v", err) 257 } 258 259 // Kill all containers that survived 260 for _, ctr := range ctrs { 261 ch := make(chan interface{}) 262 cs.SetContainerEventFilter(ctr.Id, func(e *types.Event) { 263 if e.Type == "exit" && e.Pid == "init" { 264 ch <- nil 265 } 266 }) 267 268 if err := cs.KillContainer(ctr.Id); err != nil { 269 fmt.Fprintf(os.Stderr, "Failed to cleanup leftover test containers: %v\n", err) 270 } 271 272 select { 273 case <-ch: 274 case <-time.After(3 * time.Second): 275 fmt.Fprintf(os.Stderr, "TearDownTest: Containerd %v didn't die after 3 seconds\n", ctr.Id) 276 } 277 } 278 }