github.com/akerouanton/docker@v1.11.0-rc3/libcontainerd/client_linux.go (about) 1 package libcontainerd 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "sync" 10 "syscall" 11 12 "github.com/Sirupsen/logrus" 13 containerd "github.com/docker/containerd/api/grpc/types" 14 "github.com/docker/docker/pkg/idtools" 15 "github.com/docker/docker/pkg/mount" 16 "github.com/opencontainers/specs/specs-go" 17 "golang.org/x/net/context" 18 ) 19 20 type client struct { 21 clientCommon 22 23 // Platform specific properties below here. 24 remote *remote 25 q queue 26 exitNotifiers map[string]*exitNotifier 27 } 28 29 func (clnt *client) AddProcess(containerID, processFriendlyName string, specp Process) error { 30 clnt.lock(containerID) 31 defer clnt.unlock(containerID) 32 container, err := clnt.getContainer(containerID) 33 if err != nil { 34 return err 35 } 36 37 spec, err := container.spec() 38 if err != nil { 39 return err 40 } 41 sp := spec.Process 42 sp.Args = specp.Args 43 sp.Terminal = specp.Terminal 44 if specp.Env != nil { 45 sp.Env = specp.Env 46 } 47 if specp.Cwd != nil { 48 sp.Cwd = *specp.Cwd 49 } 50 if specp.User != nil { 51 sp.User = specs.User{ 52 UID: specp.User.UID, 53 GID: specp.User.GID, 54 AdditionalGids: specp.User.AdditionalGids, 55 } 56 } 57 if specp.Capabilities != nil { 58 sp.Capabilities = specp.Capabilities 59 } 60 61 p := container.newProcess(processFriendlyName) 62 63 r := &containerd.AddProcessRequest{ 64 Args: sp.Args, 65 Cwd: sp.Cwd, 66 Terminal: sp.Terminal, 67 Id: containerID, 68 Env: sp.Env, 69 User: &containerd.User{ 70 Uid: sp.User.UID, 71 Gid: sp.User.GID, 72 AdditionalGids: sp.User.AdditionalGids, 73 }, 74 Pid: processFriendlyName, 75 Stdin: p.fifo(syscall.Stdin), 76 Stdout: p.fifo(syscall.Stdout), 77 Stderr: p.fifo(syscall.Stderr), 78 Capabilities: sp.Capabilities, 79 ApparmorProfile: sp.ApparmorProfile, 80 SelinuxLabel: sp.SelinuxLabel, 81 NoNewPrivileges: sp.NoNewPrivileges, 82 Rlimits: convertRlimits(sp.Rlimits), 83 } 84 85 iopipe, err := p.openFifos(sp.Terminal) 86 if err != nil { 87 return err 88 } 89 90 if _, err := clnt.remote.apiClient.AddProcess(context.Background(), r); err != nil { 91 p.closeFifos(iopipe) 92 return err 93 } 94 95 container.processes[processFriendlyName] = p 96 97 clnt.unlock(containerID) 98 99 if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil { 100 return err 101 } 102 clnt.lock(containerID) 103 104 return nil 105 } 106 107 func (clnt *client) prepareBundleDir(uid, gid int) (string, error) { 108 root, err := filepath.Abs(clnt.remote.stateDir) 109 if err != nil { 110 return "", err 111 } 112 if uid == 0 && gid == 0 { 113 return root, nil 114 } 115 p := string(filepath.Separator) 116 for _, d := range strings.Split(root, string(filepath.Separator))[1:] { 117 p = filepath.Join(p, d) 118 fi, err := os.Stat(p) 119 if err != nil && !os.IsNotExist(err) { 120 return "", err 121 } 122 if os.IsNotExist(err) || fi.Mode()&1 == 0 { 123 p = fmt.Sprintf("%s.%d.%d", p, uid, gid) 124 if err := idtools.MkdirAs(p, 0700, uid, gid); err != nil && !os.IsExist(err) { 125 return "", err 126 } 127 } 128 } 129 return p, nil 130 } 131 132 func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) (err error) { 133 clnt.lock(containerID) 134 defer clnt.unlock(containerID) 135 136 if ctr, err := clnt.getContainer(containerID); err == nil { 137 if ctr.restarting { // docker doesn't actually call start if restart is on atm, but probably should in the future 138 ctr.restartManager.Cancel() 139 ctr.clean() 140 } else { 141 return fmt.Errorf("Container %s is aleady active", containerID) 142 } 143 } 144 145 uid, gid, err := getRootIDs(specs.Spec(spec)) 146 if err != nil { 147 return err 148 } 149 dir, err := clnt.prepareBundleDir(uid, gid) 150 if err != nil { 151 return err 152 } 153 154 container := clnt.newContainer(filepath.Join(dir, containerID), options...) 155 if err := container.clean(); err != nil { 156 return err 157 } 158 159 defer func() { 160 if err != nil { 161 container.clean() 162 clnt.deleteContainer(containerID) 163 } 164 }() 165 166 // uid/gid 167 rootfsDir := filepath.Join(container.dir, "rootfs") 168 if err := idtools.MkdirAllAs(rootfsDir, 0700, uid, gid); err != nil && !os.IsExist(err) { 169 return err 170 } 171 if err := syscall.Mount(spec.Root.Path, rootfsDir, "bind", syscall.MS_REC|syscall.MS_BIND, ""); err != nil { 172 return err 173 } 174 spec.Root.Path = "rootfs" 175 176 f, err := os.Create(filepath.Join(container.dir, configFilename)) 177 if err != nil { 178 return err 179 } 180 defer f.Close() 181 if err := json.NewEncoder(f).Encode(spec); err != nil { 182 return err 183 } 184 185 return container.start() 186 } 187 188 func (clnt *client) Signal(containerID string, sig int) error { 189 clnt.lock(containerID) 190 defer clnt.unlock(containerID) 191 _, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{ 192 Id: containerID, 193 Pid: InitFriendlyName, 194 Signal: uint32(sig), 195 }) 196 return err 197 } 198 199 func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { 200 clnt.lock(containerID) 201 defer clnt.unlock(containerID) 202 if _, err := clnt.getContainer(containerID); err != nil { 203 return err 204 } 205 _, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{ 206 Id: containerID, 207 Pid: processFriendlyName, 208 Width: uint32(width), 209 Height: uint32(height), 210 }) 211 return err 212 } 213 214 func (clnt *client) Pause(containerID string) error { 215 return clnt.setState(containerID, StatePause) 216 } 217 218 func (clnt *client) setState(containerID, state string) error { 219 clnt.lock(containerID) 220 container, err := clnt.getContainer(containerID) 221 if err != nil { 222 clnt.unlock(containerID) 223 return err 224 } 225 if container.systemPid == 0 { 226 clnt.unlock(containerID) 227 return fmt.Errorf("No active process for container %s", containerID) 228 } 229 st := "running" 230 if state == StatePause { 231 st = "paused" 232 } 233 chstate := make(chan struct{}) 234 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 235 Id: containerID, 236 Pid: InitFriendlyName, 237 Status: st, 238 }) 239 if err != nil { 240 clnt.unlock(containerID) 241 return err 242 } 243 container.pauseMonitor.append(state, chstate) 244 clnt.unlock(containerID) 245 <-chstate 246 return nil 247 } 248 249 func (clnt *client) Resume(containerID string) error { 250 return clnt.setState(containerID, StateResume) 251 } 252 253 func (clnt *client) Stats(containerID string) (*Stats, error) { 254 resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID}) 255 if err != nil { 256 return nil, err 257 } 258 return (*Stats)(resp), nil 259 } 260 261 func (clnt *client) setExited(containerID string) error { 262 clnt.lock(containerID) 263 defer clnt.unlock(containerID) 264 265 var exitCode uint32 266 if event, ok := clnt.remote.pastEvents[containerID]; ok { 267 exitCode = event.Status 268 delete(clnt.remote.pastEvents, containerID) 269 } 270 271 err := clnt.backend.StateChanged(containerID, StateInfo{ 272 State: StateExit, 273 ExitCode: exitCode, 274 }) 275 276 // Unmount and delete the bundle folder 277 if mts, err := mount.GetMounts(); err == nil { 278 for _, mts := range mts { 279 if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") { 280 if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil { 281 os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs")) 282 } 283 break 284 } 285 } 286 } 287 288 return err 289 } 290 291 func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { 292 cont, err := clnt.getContainerdContainer(containerID) 293 if err != nil { 294 return nil, err 295 } 296 pids := make([]int, len(cont.Pids)) 297 for i, p := range cont.Pids { 298 pids[i] = int(p) 299 } 300 return pids, nil 301 } 302 303 // Summary returns a summary of the processes running in a container. 304 // This is a no-op on Linux. 305 func (clnt *client) Summary(containerID string) ([]Summary, error) { 306 return nil, nil 307 } 308 309 func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) { 310 resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID}) 311 if err != nil { 312 return nil, err 313 } 314 for _, cont := range resp.Containers { 315 if cont.Id == containerID { 316 return cont, nil 317 } 318 } 319 return nil, fmt.Errorf("invalid state response") 320 } 321 322 func (clnt *client) newContainer(dir string, options ...CreateOption) *container { 323 container := &container{ 324 containerCommon: containerCommon{ 325 process: process{ 326 dir: dir, 327 processCommon: processCommon{ 328 containerID: filepath.Base(dir), 329 client: clnt, 330 friendlyName: InitFriendlyName, 331 }, 332 }, 333 processes: make(map[string]*process), 334 }, 335 } 336 for _, option := range options { 337 if err := option.Apply(container); err != nil { 338 logrus.Error(err) 339 } 340 } 341 return container 342 } 343 344 func (clnt *client) UpdateResources(containerID string, resources Resources) error { 345 clnt.lock(containerID) 346 defer clnt.unlock(containerID) 347 container, err := clnt.getContainer(containerID) 348 if err != nil { 349 return err 350 } 351 if container.systemPid == 0 { 352 return fmt.Errorf("No active process for container %s", containerID) 353 } 354 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 355 Id: containerID, 356 Pid: InitFriendlyName, 357 Resources: (*containerd.UpdateResource)(&resources), 358 }) 359 if err != nil { 360 return err 361 } 362 return nil 363 } 364 365 func (clnt *client) getExitNotifier(containerID string) *exitNotifier { 366 clnt.mapMutex.RLock() 367 defer clnt.mapMutex.RUnlock() 368 return clnt.exitNotifiers[containerID] 369 } 370 371 func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier { 372 clnt.mapMutex.Lock() 373 w, ok := clnt.exitNotifiers[containerID] 374 defer clnt.mapMutex.Unlock() 375 if !ok { 376 w = &exitNotifier{c: make(chan struct{}), client: clnt} 377 clnt.exitNotifiers[containerID] = w 378 } 379 return w 380 } 381 382 type exitNotifier struct { 383 id string 384 client *client 385 c chan struct{} 386 once sync.Once 387 } 388 389 func (en *exitNotifier) close() { 390 en.once.Do(func() { 391 close(en.c) 392 en.client.mapMutex.Lock() 393 if en == en.client.exitNotifiers[en.id] { 394 delete(en.client.exitNotifiers, en.id) 395 } 396 en.client.mapMutex.Unlock() 397 }) 398 } 399 func (en *exitNotifier) wait() <-chan struct{} { 400 return en.c 401 }