Merge pull request #1309 from Random-Liu/update-containerd

Update containerd to a6a0c8b6e3.
This commit is contained in:
Lantao Liu 2019-10-10 15:15:45 -07:00 committed by GitHub
commit 815f3e727d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2919 additions and 1111 deletions

View File

@ -17,13 +17,11 @@ limitations under the License.
package integration package integration
import ( import (
"os"
"os/exec"
"sort" "sort"
"testing" "testing"
"time"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/errdefs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -127,7 +125,9 @@ func TestContainerdRestart(t *testing.T) {
task, err := cntr.Task(ctx, nil) task, err := cntr.Task(ctx, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = task.Delete(ctx, containerd.WithProcessKill) _, err = task.Delete(ctx, containerd.WithProcessKill)
require.NoError(t, err) if err != nil {
require.True(t, errdefs.IsNotFound(err))
}
} }
} }
@ -196,149 +196,4 @@ func TestContainerdRestart(t *testing.T) {
} }
} }
// Note: The test moves runc binary. // TODO: Add back the unknown state test.
// The test requires:
// 1) The runtime is runc;
// 2) runc is in PATH;
func TestUnknownStateAfterContainerdRestart(t *testing.T) {
if *runtimeHandler != "" {
t.Skip("unsupported config: runtime handler is set")
}
runcPath, err := exec.LookPath("runc")
if err != nil {
t.Skip("unsupported config: runc not in PATH")
}
sbConfig := PodSandboxConfig("sandbox", "sandbox-unknown-state")
const testImage = "busybox"
t.Logf("Pull test image %q", testImage)
img, err := imageService.PullImage(&runtime.ImageSpec{Image: testImage}, nil, sbConfig)
require.NoError(t, err)
defer func() {
assert.NoError(t, imageService.RemoveImage(&runtime.ImageSpec{Image: img}))
}()
t.Log("Should not be able to create sandbox without runc")
tmpRuncPath := Randomize(runcPath)
require.NoError(t, os.Rename(runcPath, tmpRuncPath))
defer func() {
os.Rename(tmpRuncPath, runcPath)
}()
sb, err := runtimeService.RunPodSandbox(sbConfig, "")
if err == nil {
assert.NoError(t, runtimeService.StopPodSandbox(sb))
assert.NoError(t, runtimeService.RemovePodSandbox(sb))
t.Skip("unsupported config: runc is not being used")
}
require.NoError(t, os.Rename(tmpRuncPath, runcPath))
t.Log("Create a sandbox")
sb, err = runtimeService.RunPodSandbox(sbConfig, "")
require.NoError(t, err)
defer func() {
// Make sure the sandbox is cleaned up in any case.
runtimeService.StopPodSandbox(sb)
runtimeService.RemovePodSandbox(sb)
}()
ps, err := runtimeService.PodSandboxStatus(sb)
require.NoError(t, err)
assert.Equal(t, runtime.PodSandboxState_SANDBOX_READY, ps.GetState())
t.Log("Create a container")
cnConfig := ContainerConfig(
"container-unknown-state",
testImage,
WithCommand("sleep", "1000"),
)
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
require.NoError(t, err)
t.Log("Start the container")
require.NoError(t, runtimeService.StartContainer(cn))
cs, err := runtimeService.ContainerStatus(cn)
require.NoError(t, err)
assert.Equal(t, runtime.ContainerState_CONTAINER_RUNNING, cs.GetState())
t.Log("Move runc binary, so that container/sandbox can't be loaded after restart")
tmpRuncPath = Randomize(runcPath)
require.NoError(t, os.Rename(runcPath, tmpRuncPath))
defer func() {
os.Rename(tmpRuncPath, runcPath)
}()
t.Log("Restart containerd")
RestartContainerd(t)
t.Log("Sandbox should be in NOTREADY state after containerd restart")
ps, err = runtimeService.PodSandboxStatus(sb)
require.NoError(t, err)
assert.Equal(t, runtime.PodSandboxState_SANDBOX_NOTREADY, ps.GetState())
t.Log("Container should be in UNKNOWN state after containerd restart")
cs, err = runtimeService.ContainerStatus(cn)
require.NoError(t, err)
assert.Equal(t, runtime.ContainerState_CONTAINER_UNKNOWN, cs.GetState())
t.Log("Stop/remove the sandbox should fail for the lack of runc")
assert.Error(t, runtimeService.StopPodSandbox(sb))
assert.Error(t, runtimeService.RemovePodSandbox(sb))
t.Log("Stop/remove the container should fail for the lack of runc")
assert.Error(t, runtimeService.StopContainer(cn, 10))
assert.Error(t, runtimeService.RemoveContainer(cn))
t.Log("Move runc back")
require.NoError(t, os.Rename(tmpRuncPath, runcPath))
t.Log("Sandbox should still be in NOTREADY state")
ps, err = runtimeService.PodSandboxStatus(sb)
require.NoError(t, err)
assert.Equal(t, runtime.PodSandboxState_SANDBOX_NOTREADY, ps.GetState())
t.Log("Container should still be in UNKNOWN state")
cs, err = runtimeService.ContainerStatus(cn)
require.NoError(t, err)
assert.Equal(t, runtime.ContainerState_CONTAINER_UNKNOWN, cs.GetState())
t.Log("Sandbox operations which require running state should fail")
_, err = runtimeService.PortForward(&runtime.PortForwardRequest{
PodSandboxId: sb,
Port: []int32{8080},
})
assert.Error(t, err)
t.Log("Container operations which require running state should fail")
assert.Error(t, runtimeService.ReopenContainerLog(cn))
_, _, err = runtimeService.ExecSync(cn, []string{"ls"}, 10*time.Second)
assert.Error(t, err)
_, err = runtimeService.Attach(&runtime.AttachRequest{
ContainerId: cn,
Stdin: true,
Stdout: true,
Stderr: true,
})
assert.Error(t, err)
t.Log("Containerd should still be running now")
_, err = runtimeService.Status()
require.NoError(t, err)
t.Log("Remove the container should fail in this state")
assert.Error(t, runtimeService.RemoveContainer(cn))
t.Log("Remove the sandbox should fail in this state")
assert.Error(t, runtimeService.RemovePodSandbox(sb))
t.Log("Should be able to stop container in this state")
assert.NoError(t, runtimeService.StopContainer(cn, 10))
t.Log("Should be able to stop sandbox in this state")
assert.NoError(t, runtimeService.StopPodSandbox(sb))
t.Log("Should be able to remove container after stop")
assert.NoError(t, runtimeService.RemoveContainer(cn))
t.Log("Should be able to remove sandbox after stop")
assert.NoError(t, runtimeService.RemovePodSandbox(sb))
}

View File

@ -21,30 +21,30 @@ package server
import ( import (
"testing" "testing"
"github.com/containerd/cgroups" v1 "github.com/containerd/cgroups/stats/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGetWorkingSet(t *testing.T) { func TestGetWorkingSet(t *testing.T) {
for desc, test := range map[string]struct { for desc, test := range map[string]struct {
memory *cgroups.MemoryStat memory *v1.MemoryStat
expected uint64 expected uint64
}{ }{
"nil memory usage": { "nil memory usage": {
memory: &cgroups.MemoryStat{}, memory: &v1.MemoryStat{},
expected: 0, expected: 0,
}, },
"memory usage higher than inactive_total_file": { "memory usage higher than inactive_total_file": {
memory: &cgroups.MemoryStat{ memory: &v1.MemoryStat{
TotalInactiveFile: 1000, TotalInactiveFile: 1000,
Usage: &cgroups.MemoryEntry{Usage: 2000}, Usage: &v1.MemoryEntry{Usage: 2000},
}, },
expected: 1000, expected: 1000,
}, },
"memory usage lower than inactive_total_file": { "memory usage lower than inactive_total_file": {
memory: &cgroups.MemoryStat{ memory: &v1.MemoryStat{
TotalInactiveFile: 2000, TotalInactiveFile: 2000,
Usage: &cgroups.MemoryEntry{Usage: 1000}, Usage: &v1.MemoryEntry{Usage: 1000},
}, },
expected: 0, expected: 0,
}, },

View File

@ -22,7 +22,7 @@ github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c
github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823 github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
github.com/opencontainers/runc f4982d86f7fde0b6f953cc62ccc4022c519a10a9 # v1.0.0-rc8-32-gf4982d86 github.com/opencontainers/runc d736ef14f0288d6993a1845745d6756cfc9ddd5a # v1.0.0-rc9
github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7 github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
github.com/matttproud/golang_protobuf_extensions v1.0.1 github.com/matttproud/golang_protobuf_extensions v1.0.1
@ -44,9 +44,9 @@ github.com/containerd/ttrpc 92c8520ef9f86600c650dd540266a007bf03670f
github.com/containerd/go-runc 9007c2405372fe28918845901a3276c0915689a1 github.com/containerd/go-runc 9007c2405372fe28918845901a3276c0915689a1
github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13 github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c
github.com/containerd/containerd ed16170c4c399c57f25d6aa1e97b345ed6ab96cb github.com/containerd/containerd a6a0c8b6e36415a151d93d096c1c0af9e0bd7977
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9 github.com/containerd/cgroups abd0b19954a6b05e0963f48427062d1481b7faad
github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
github.com/Microsoft/hcsshim c088f411aaf3585d8dffc9deb4289ffa32854497 # TODO(windows): update this in containerd/containerd github.com/Microsoft/hcsshim c088f411aaf3585d8dffc9deb4289ffa32854497 # TODO(windows): update this in containerd/containerd
github.com/Microsoft/go-winio v0.4.14 github.com/Microsoft/go-winio v0.4.14

View File

@ -26,17 +26,33 @@ import (
"strconv" "strconv"
"strings" "strings"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
func NewBlkio(root string) *blkioController { // NewBlkio returns a Blkio controller given the root folder of cgroups.
return &blkioController{ // It may optionally accept other configuration options, such as ProcRoot(path)
func NewBlkio(root string, options ...func(controller *blkioController)) *blkioController {
ctrl := &blkioController{
root: filepath.Join(root, string(Blkio)), root: filepath.Join(root, string(Blkio)),
procRoot: "/proc",
}
for _, opt := range options {
opt(ctrl)
}
return ctrl
}
// ProcRoot overrides the default location of the "/proc" filesystem
func ProcRoot(path string) func(controller *blkioController) {
return func(c *blkioController) {
c.procRoot = path
} }
} }
type blkioController struct { type blkioController struct {
root string root string
procRoot string
} }
func (b *blkioController) Name() Name { func (b *blkioController) Name() Name {
@ -72,8 +88,8 @@ func (b *blkioController) Update(path string, resources *specs.LinuxResources) e
return b.Create(path, resources) return b.Create(path, resources)
} }
func (b *blkioController) Stat(path string, stats *Metrics) error { func (b *blkioController) Stat(path string, stats *v1.Metrics) error {
stats.Blkio = &BlkIOStat{} stats.Blkio = &v1.BlkIOStat{}
settings := []blkioStatSettings{ settings := []blkioStatSettings{
{ {
name: "throttle.io_serviced", name: "throttle.io_serviced",
@ -122,7 +138,7 @@ func (b *blkioController) Stat(path string, stats *Metrics) error {
}, },
) )
} }
f, err := os.Open("/proc/diskstats") f, err := os.Open(filepath.Join(b.procRoot, "diskstats"))
if err != nil { if err != nil {
return err return err
} }
@ -141,7 +157,7 @@ func (b *blkioController) Stat(path string, stats *Metrics) error {
return nil return nil
} }
func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]*BlkIOEntry) error { func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]*v1.BlkIOEntry) error {
f, err := os.Open(filepath.Join(b.Path(path), fmt.Sprintf("blkio.%s", name))) f, err := os.Open(filepath.Join(b.Path(path), fmt.Sprintf("blkio.%s", name)))
if err != nil { if err != nil {
return err return err
@ -180,7 +196,7 @@ func (b *blkioController) readEntry(devices map[deviceKey]string, path, name str
if err != nil { if err != nil {
return err return err
} }
*entry = append(*entry, &BlkIOEntry{ *entry = append(*entry, &v1.BlkIOEntry{
Device: devices[deviceKey{major, minor}], Device: devices[deviceKey{major, minor}],
Major: major, Major: major,
Minor: minor, Minor: minor,
@ -268,7 +284,7 @@ type blkioSettings struct {
type blkioStatSettings struct { type blkioStatSettings struct {
name string name string
entry *[]*BlkIOEntry entry *[]*v1.BlkIOEntry
} }
func uintf(v interface{}) []byte { func uintf(v interface{}) []byte {

View File

@ -25,6 +25,7 @@ import (
"strings" "strings"
"sync" "sync"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -246,7 +247,7 @@ func (c *cgroup) Delete() error {
} }
// Stat returns the current metrics for the cgroup // Stat returns the current metrics for the cgroup
func (c *cgroup) Stat(handlers ...ErrorHandler) (*Metrics, error) { func (c *cgroup) Stat(handlers ...ErrorHandler) (*v1.Metrics, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if c.err != nil { if c.err != nil {
@ -256,10 +257,10 @@ func (c *cgroup) Stat(handlers ...ErrorHandler) (*Metrics, error) {
handlers = append(handlers, errPassthrough) handlers = append(handlers, errPassthrough)
} }
var ( var (
stats = &Metrics{ stats = &v1.Metrics{
CPU: &CPUStat{ CPU: &v1.CPUStat{
Throttling: &Throttle{}, Throttling: &v1.Throttle{},
Usage: &CPUUsage{}, Usage: &v1.CPUUsage{},
}, },
} }
wg = &sync.WaitGroup{} wg = &sync.WaitGroup{}

View File

@ -19,6 +19,7 @@ package cgroups
import ( import (
"os" "os"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -68,7 +69,7 @@ type Cgroup interface {
// subsystems are moved one at a time // subsystems are moved one at a time
MoveTo(Cgroup) error MoveTo(Cgroup) error
// Stat returns the stats for all subsystems in the cgroup // Stat returns the stats for all subsystems in the cgroup
Stat(...ErrorHandler) (*Metrics, error) Stat(...ErrorHandler) (*v1.Metrics, error)
// Update updates all the subsystems with the provided resource changes // Update updates all the subsystems with the provided resource changes
Update(resources *specs.LinuxResources) error Update(resources *specs.LinuxResources) error
// Processes returns all the processes in a select subsystem for the cgroup // Processes returns all the processes in a select subsystem for the cgroup

View File

@ -24,6 +24,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -100,7 +101,7 @@ func (c *cpuController) Update(path string, resources *specs.LinuxResources) err
return c.Create(path, resources) return c.Create(path, resources)
} }
func (c *cpuController) Stat(path string, stats *Metrics) error { func (c *cpuController) Stat(path string, stats *v1.Metrics) error {
f, err := os.Open(filepath.Join(c.Path(path), "cpu.stat")) f, err := os.Open(filepath.Join(c.Path(path), "cpu.stat"))
if err != nil { if err != nil {
return err return err

View File

@ -22,6 +22,8 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
v1 "github.com/containerd/cgroups/stats/v1"
) )
const nanosecondsInSecond = 1000000000 const nanosecondsInSecond = 1000000000
@ -46,7 +48,7 @@ func (c *cpuacctController) Path(path string) string {
return filepath.Join(c.root, path) return filepath.Join(c.root, path)
} }
func (c *cpuacctController) Stat(path string, stats *Metrics) error { func (c *cpuacctController) Stat(path string, stats *v1.Metrics) error {
user, kernel, err := c.getUsage(path) user, kernel, err := c.getUsage(path)
if err != nil { if err != nil {
return err return err

13
vendor/github.com/containerd/cgroups/go.mod generated vendored Normal file
View File

@ -0,0 +1,13 @@
module github.com/containerd/cgroups
go 1.12
require (
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/docker/go-units v0.4.0
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e
github.com/gogo/protobuf v1.2.1
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700
github.com/pkg/errors v0.8.1
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f
)

View File

@ -23,6 +23,7 @@ import (
"strconv" "strconv"
"strings" "strings"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -67,7 +68,7 @@ func (h *hugetlbController) Create(path string, resources *specs.LinuxResources)
return nil return nil
} }
func (h *hugetlbController) Stat(path string, stats *Metrics) error { func (h *hugetlbController) Stat(path string, stats *v1.Metrics) error {
for _, size := range h.sizes { for _, size := range h.sizes {
s, err := h.readSizeStat(path, size) s, err := h.readSizeStat(path, size)
if err != nil { if err != nil {
@ -78,8 +79,8 @@ func (h *hugetlbController) Stat(path string, stats *Metrics) error {
return nil return nil
} }
func (h *hugetlbController) readSizeStat(path, size string) (*HugetlbStat, error) { func (h *hugetlbController) readSizeStat(path, size string) (*v1.HugetlbStat, error) {
s := HugetlbStat{ s := v1.HugetlbStat{
Pagesize: size, Pagesize: size,
} }
for _, t := range []struct { for _, t := range []struct {

View File

@ -27,19 +27,48 @@ import (
"strings" "strings"
"syscall" "syscall"
"golang.org/x/sys/unix" v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
) )
func NewMemory(root string) *memoryController { // NewMemory returns a Memory controller given the root folder of cgroups.
return &memoryController{ // It may optionally accept other configuration options, such as IgnoreModules(...)
func NewMemory(root string, options ...func(*memoryController)) *memoryController {
mc := &memoryController{
root: filepath.Join(root, string(Memory)), root: filepath.Join(root, string(Memory)),
ignored: map[string]struct{}{},
}
for _, opt := range options {
opt(mc)
}
return mc
}
// IgnoreModules configure the memory controller to not read memory metrics for some
// module names (e.g. passing "memsw" would avoid all the memory.memsw.* entries)
func IgnoreModules(names ...string) func(*memoryController) {
return func(mc *memoryController) {
for _, name := range names {
mc.ignored[name] = struct{}{}
}
}
}
// OptionalSwap allows the memory controller to not fail if cgroups is not accounting
// Swap memory (there are no memory.memsw.* entries)
func OptionalSwap() func(*memoryController) {
return func(mc *memoryController) {
_, err := os.Stat(filepath.Join(mc.root, "memory.memsw.usage_in_bytes"))
if os.IsNotExist(err) {
mc.ignored["memsw"] = struct{}{}
}
} }
} }
type memoryController struct { type memoryController struct {
root string root string
ignored map[string]struct{}
} }
func (m *memoryController) Name() Name { func (m *memoryController) Name() Name {
@ -97,24 +126,24 @@ func (m *memoryController) Update(path string, resources *specs.LinuxResources)
return m.set(path, settings) return m.set(path, settings)
} }
func (m *memoryController) Stat(path string, stats *Metrics) error { func (m *memoryController) Stat(path string, stats *v1.Metrics) error {
f, err := os.Open(filepath.Join(m.Path(path), "memory.stat")) f, err := os.Open(filepath.Join(m.Path(path), "memory.stat"))
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
stats.Memory = &MemoryStat{ stats.Memory = &v1.MemoryStat{
Usage: &MemoryEntry{}, Usage: &v1.MemoryEntry{},
Swap: &MemoryEntry{}, Swap: &v1.MemoryEntry{},
Kernel: &MemoryEntry{}, Kernel: &v1.MemoryEntry{},
KernelTCP: &MemoryEntry{}, KernelTCP: &v1.MemoryEntry{},
} }
if err := m.parseStats(f, stats.Memory); err != nil { if err := m.parseStats(f, stats.Memory); err != nil {
return err return err
} }
for _, t := range []struct { for _, t := range []struct {
module string module string
entry *MemoryEntry entry *v1.MemoryEntry
}{ }{
{ {
module: "", module: "",
@ -133,6 +162,9 @@ func (m *memoryController) Stat(path string, stats *Metrics) error {
entry: stats.Memory.KernelTCP, entry: stats.Memory.KernelTCP,
}, },
} { } {
if _, ok := m.ignored[t.module]; ok {
continue
}
for _, tt := range []struct { for _, tt := range []struct {
name string name string
value *uint64 value *uint64
@ -197,7 +229,7 @@ func writeEventFD(root string, cfd, efd uintptr) error {
return err return err
} }
func (m *memoryController) parseStats(r io.Reader, stat *MemoryStat) error { func (m *memoryController) parseStats(r io.Reader, stat *v1.MemoryStat) error {
var ( var (
raw = make(map[string]uint64) raw = make(map[string]uint64)
sc = bufio.NewScanner(r) sc = bufio.NewScanner(r)

View File

@ -23,6 +23,7 @@ import (
"strconv" "strconv"
"strings" "strings"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -62,7 +63,7 @@ func (p *pidsController) Update(path string, resources *specs.LinuxResources) er
return p.Create(path, resources) return p.Create(path, resources)
} }
func (p *pidsController) Stat(path string, stats *Metrics) error { func (p *pidsController) Stat(path string, stats *v1.Metrics) error {
current, err := readUint(filepath.Join(p.Path(path), "pids.current")) current, err := readUint(filepath.Join(p.Path(path), "pids.current"))
if err != nil { if err != nil {
return err return err
@ -77,7 +78,7 @@ func (p *pidsController) Stat(path string, stats *Metrics) error {
return err return err
} }
} }
stats.Pids = &PidsStat{ stats.Pids = &v1.PidsStat{
Current: current, Current: current,
Limit: max, Limit: max,
} }

View File

@ -24,6 +24,7 @@ import (
"strconv" "strconv"
"strings" "strings"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -80,7 +81,7 @@ func (p *rdmaController) Update(path string, resources *specs.LinuxResources) er
return p.Create(path, resources) return p.Create(path, resources)
} }
func parseRdmaKV(raw string, entry *RdmaEntry) { func parseRdmaKV(raw string, entry *v1.RdmaEntry) {
var value uint64 var value uint64
var err error var err error
@ -103,13 +104,13 @@ func parseRdmaKV(raw string, entry *RdmaEntry) {
} }
} }
func toRdmaEntry(strEntries []string) []*RdmaEntry { func toRdmaEntry(strEntries []string) []*v1.RdmaEntry {
var rdmaEntries []*RdmaEntry var rdmaEntries []*v1.RdmaEntry
for i := range strEntries { for i := range strEntries {
parts := strings.Fields(strEntries[i]) parts := strings.Fields(strEntries[i])
switch len(parts) { switch len(parts) {
case 3: case 3:
entry := new(RdmaEntry) entry := new(v1.RdmaEntry)
entry.Device = parts[0] entry.Device = parts[0]
parseRdmaKV(parts[1], entry) parseRdmaKV(parts[1], entry)
parseRdmaKV(parts[2], entry) parseRdmaKV(parts[2], entry)
@ -122,7 +123,7 @@ func toRdmaEntry(strEntries []string) []*RdmaEntry {
return rdmaEntries return rdmaEntries
} }
func (p *rdmaController) Stat(path string, stats *Metrics) error { func (p *rdmaController) Stat(path string, stats *v1.Metrics) error {
currentData, err := ioutil.ReadFile(filepath.Join(p.Path(path), "rdma.current")) currentData, err := ioutil.ReadFile(filepath.Join(p.Path(path), "rdma.current"))
if err != nil { if err != nil {
@ -145,7 +146,7 @@ func (p *rdmaController) Stat(path string, stats *Metrics) error {
currentEntries := toRdmaEntry(currentPerDevices) currentEntries := toRdmaEntry(currentPerDevices)
maxEntries := toRdmaEntry(maxPerDevices) maxEntries := toRdmaEntry(maxPerDevices)
stats.Rdma = &RdmaStat{ stats.Rdma = &v1.RdmaStat{
Current: currentEntries, Current: currentEntries,
Limit: maxEntries, Limit: maxEntries,
} }

17
vendor/github.com/containerd/cgroups/stats/v1/doc.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1

View File

@ -19,6 +19,7 @@ package cgroups
import ( import (
"fmt" "fmt"
v1 "github.com/containerd/cgroups/stats/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -85,7 +86,7 @@ type deleter interface {
type stater interface { type stater interface {
Subsystem Subsystem
Stat(path string, stats *Metrics) error Stat(path string, stats *v1.Metrics) error
} }
type updater interface { type updater interface {

View File

@ -210,6 +210,34 @@ See [PLUGINS.md](PLUGINS.md) for how to create plugins
Please see [RELEASES.md](RELEASES.md) for details on versioning and stability Please see [RELEASES.md](RELEASES.md) for details on versioning and stability
of containerd components. of containerd components.
Downloadable 64-bit Intel/AMD binaries of all official releases are available on
our [releases page](https://github.com/containerd/containerd/releases), as well as
auto-published to the [cri-containerd-release storage bucket](https://console.cloud.google.com/storage/browser/cri-containerd-release?pli=1).
For other architectures and distribution support, you will find that many
Linux distributions package their own containerd and provide it across several
architectures, such as [Canonical's Ubuntu packaging](https://launchpad.net/ubuntu/bionic/+package/containerd).
#### Enabling command auto-completion
Starting with containerd 1.4, the urfave client feature for auto-creation of bash
autocompletion data is enabled. To use the autocomplete feature in your shell, source
the autocomplete/bash_autocomplete file in your .bashrc file while setting the `PROG`
variable to `ctr`:
```
$ PROG=ctr source vendor/github.com/urfave/cli/autocomplete/bash_autocomplete
```
#### Distribution of `ctr` autocomplete for bash
Copy `vendor/github.com/urfave/cli/autocomplete/bash_autocomplete` into
`/etc/bash_completion.d/` and rename it to `ctr`.
Provide documentation to users to `source` this file into their shell if
you don't place the autocomplete file in a location where it is automatically
loaded for user's bash shell environment.
### Communication ### Communication
For async communication and long running discussions please use issues and pull requests on the github repo. For async communication and long running discussions please use issues and pull requests on the github repo.

View File

@ -29,7 +29,7 @@ func serviceFlags() []cli.Flag {
return nil return nil
} }
// applyPlatformFlags applys platform-specific flags. // applyPlatformFlags applies platform-specific flags.
func applyPlatformFlags(context *cli.Context) { func applyPlatformFlags(context *cli.Context) {
} }

View File

@ -263,7 +263,7 @@ func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descri
images.HandlerFunc(exportHandler), images.HandlerFunc(exportHandler),
) )
// Walk sequentially since the number of fetchs is likely one and doing in // Walk sequentially since the number of fetches is likely one and doing in
// parallel requires locking the export handler // parallel requires locking the export handler
if err := images.Walk(ctx, handlers, desc); err != nil { if err := images.Walk(ctx, handlers, desc); err != nil {
return nil, err return nil, err

View File

@ -124,7 +124,7 @@ func ImportIndex(ctx context.Context, store content.Store, reader io.Reader, opt
} }
// If OCI layout was given, interpret the tar as an OCI layout. // If OCI layout was given, interpret the tar as an OCI layout.
// When not provided, the layout of the tar will be interpretted // When not provided, the layout of the tar will be interpreted
// as Docker v1.1 or v1.2. // as Docker v1.1 or v1.2.
if ociLayout.Version != "" { if ociLayout.Version != "" {
if ociLayout.Version != ocispec.ImageLayoutVersion { if ociLayout.Version != ocispec.ImageLayoutVersion {

View File

@ -20,7 +20,7 @@ import (
"strings" "strings"
"github.com/containerd/containerd/reference" "github.com/containerd/containerd/reference"
distref "github.com/docker/distribution/reference" distref "github.com/containerd/containerd/reference/docker"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
) )

View File

@ -24,7 +24,7 @@ import (
) )
// WithLease attaches a lease on the context // WithLease attaches a lease on the context
func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.Context) error, error) { func (c *Client) WithLease(ctx context.Context, opts ...leases.Opt) (context.Context, func(context.Context) error, error) {
_, ok := leases.FromContext(ctx) _, ok := leases.FromContext(ctx)
if ok { if ok {
return ctx, func(context.Context) error { return ctx, func(context.Context) error {
@ -34,7 +34,15 @@ func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.C
ls := c.LeasesService() ls := c.LeasesService()
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour)) if len(opts) == 0 {
// Use default lease configuration if no options provided
opts = []leases.Opt{
leases.WithRandomID(),
leases.WithExpiration(24 * time.Hour),
}
}
l, err := ls.Create(ctx, opts...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -19,6 +19,7 @@ package metadata
import ( import (
"context" "context"
"strings" "strings"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
@ -35,13 +36,13 @@ import (
) )
type containerStore struct { type containerStore struct {
tx *bolt.Tx db *DB
} }
// NewContainerStore returns a Store backed by an underlying bolt DB // NewContainerStore returns a Store backed by an underlying bolt DB
func NewContainerStore(tx *bolt.Tx) containers.Store { func NewContainerStore(db *DB) containers.Store {
return &containerStore{ return &containerStore{
tx: tx, db: db,
} }
} }
@ -51,14 +52,21 @@ func (s *containerStore) Get(ctx context.Context, id string) (containers.Contain
return containers.Container{}, err return containers.Container{}, err
} }
bkt := getContainerBucket(s.tx, namespace, id) container := containers.Container{ID: id}
if err := view(ctx, s.db, func(tx *bolt.Tx) error {
bkt := getContainerBucket(tx, namespace, id)
if bkt == nil { if bkt == nil {
return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "container %q in namespace %q", id, namespace) return errors.Wrapf(errdefs.ErrNotFound, "container %q in namespace %q", id, namespace)
} }
container := containers.Container{ID: id}
if err := readContainer(&container, bkt); err != nil { if err := readContainer(&container, bkt); err != nil {
return containers.Container{}, errors.Wrapf(err, "failed to read container %q", id) return errors.Wrapf(err, "failed to read container %q", id)
}
return nil
}); err != nil {
return containers.Container{}, err
} }
return container, nil return container, nil
@ -75,13 +83,15 @@ func (s *containerStore) List(ctx context.Context, fs ...string) ([]containers.C
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error()) return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
} }
bkt := getContainersBucket(s.tx, namespace) var m []containers.Container
if err := view(ctx, s.db, func(tx *bolt.Tx) error {
bkt := getContainersBucket(tx, namespace)
if bkt == nil { if bkt == nil {
return nil, nil // empty store return nil // empty store
} }
var m []containers.Container return bkt.ForEach(func(k, v []byte) error {
if err := bkt.ForEach(func(k, v []byte) error {
cbkt := bkt.Bucket(k) cbkt := bkt.Bucket(k)
if cbkt == nil { if cbkt == nil {
return nil return nil
@ -96,6 +106,7 @@ func (s *containerStore) List(ctx context.Context, fs ...string) ([]containers.C
m = append(m, container) m = append(m, container)
} }
return nil return nil
})
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
@ -113,9 +124,10 @@ func (s *containerStore) Create(ctx context.Context, container containers.Contai
return containers.Container{}, errors.Wrap(err, "create container failed validation") return containers.Container{}, errors.Wrap(err, "create container failed validation")
} }
bkt, err := createContainersBucket(s.tx, namespace) if err := update(ctx, s.db, func(tx *bolt.Tx) error {
bkt, err := createContainersBucket(tx, namespace)
if err != nil { if err != nil {
return containers.Container{}, err return err
} }
cbkt, err := bkt.CreateBucket([]byte(container.ID)) cbkt, err := bkt.CreateBucket([]byte(container.ID))
@ -123,13 +135,18 @@ func (s *containerStore) Create(ctx context.Context, container containers.Contai
if err == bolt.ErrBucketExists { if err == bolt.ErrBucketExists {
err = errors.Wrapf(errdefs.ErrAlreadyExists, "container %q", container.ID) err = errors.Wrapf(errdefs.ErrAlreadyExists, "container %q", container.ID)
} }
return containers.Container{}, err return err
} }
container.CreatedAt = time.Now().UTC() container.CreatedAt = time.Now().UTC()
container.UpdatedAt = container.CreatedAt container.UpdatedAt = container.CreatedAt
if err := writeContainer(cbkt, &container); err != nil { if err := writeContainer(cbkt, &container); err != nil {
return containers.Container{}, errors.Wrapf(err, "failed to write container %q", container.ID) return errors.Wrapf(err, "failed to write container %q", container.ID)
}
return nil
}); err != nil {
return containers.Container{}, err
} }
return container, nil return container, nil
@ -145,19 +162,20 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "must specify a container id") return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "must specify a container id")
} }
bkt := getContainersBucket(s.tx, namespace) var updated containers.Container
if err := update(ctx, s.db, func(tx *bolt.Tx) error {
bkt := getContainersBucket(tx, namespace)
if bkt == nil { if bkt == nil {
return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "cannot update container %q in namespace %q", container.ID, namespace) return errors.Wrapf(errdefs.ErrNotFound, "cannot update container %q in namespace %q", container.ID, namespace)
} }
cbkt := bkt.Bucket([]byte(container.ID)) cbkt := bkt.Bucket([]byte(container.ID))
if cbkt == nil { if cbkt == nil {
return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "container %q", container.ID) return errors.Wrapf(errdefs.ErrNotFound, "container %q", container.ID)
} }
var updated containers.Container
if err := readContainer(&updated, cbkt); err != nil { if err := readContainer(&updated, cbkt); err != nil {
return updated, errors.Wrapf(err, "failed to read container %q", container.ID) return errors.Wrapf(err, "failed to read container %q", container.ID)
} }
createdat := updated.CreatedAt createdat := updated.CreatedAt
updated.ID = container.ID updated.ID = container.ID
@ -170,11 +188,11 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai
// are provided. This allows these fields to become mutable in the // are provided. This allows these fields to become mutable in the
// future. // future.
if updated.Snapshotter != container.Snapshotter { if updated.Snapshotter != container.Snapshotter {
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter field is immutable") return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter field is immutable")
} }
if updated.Runtime.Name != container.Runtime.Name { if updated.Runtime.Name != container.Runtime.Name {
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "container.Runtime.Name field is immutable") return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Runtime.Name field is immutable")
} }
} }
@ -212,18 +230,23 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai
case "snapshotkey": case "snapshotkey":
updated.SnapshotKey = container.SnapshotKey updated.SnapshotKey = container.SnapshotKey
default: default:
return containers.Container{}, errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on %q", path, container.ID) return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on %q", path, container.ID)
} }
} }
if err := validateContainer(&updated); err != nil { if err := validateContainer(&updated); err != nil {
return containers.Container{}, errors.Wrap(err, "update failed validation") return errors.Wrap(err, "update failed validation")
} }
updated.CreatedAt = createdat updated.CreatedAt = createdat
updated.UpdatedAt = time.Now().UTC() updated.UpdatedAt = time.Now().UTC()
if err := writeContainer(cbkt, &updated); err != nil { if err := writeContainer(cbkt, &updated); err != nil {
return containers.Container{}, errors.Wrapf(err, "failed to write container %q", container.ID) return errors.Wrapf(err, "failed to write container %q", container.ID)
}
return nil
}); err != nil {
return containers.Container{}, err
} }
return updated, nil return updated, nil
@ -235,17 +258,25 @@ func (s *containerStore) Delete(ctx context.Context, id string) error {
return err return err
} }
bkt := getContainersBucket(s.tx, namespace) return update(ctx, s.db, func(tx *bolt.Tx) error {
bkt := getContainersBucket(tx, namespace)
if bkt == nil { if bkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "cannot delete container %q in namespace %q", id, namespace) return errors.Wrapf(errdefs.ErrNotFound, "cannot delete container %q in namespace %q", id, namespace)
} }
if err := bkt.DeleteBucket([]byte(id)); err == bolt.ErrBucketNotFound { if err := bkt.DeleteBucket([]byte(id)); err != nil {
return errors.Wrapf(errdefs.ErrNotFound, "container %v", id) if err == bolt.ErrBucketNotFound {
err = errors.Wrapf(errdefs.ErrNotFound, "container %v", id)
} }
return err return err
} }
atomic.AddUint32(&s.db.dirty, 1)
return nil
})
}
func validateContainer(container *containers.Container) error { func validateContainer(container *containers.Container) error {
if err := identifiers.Validate(container.ID); err != nil { if err := identifiers.Validate(container.ID); err != nil {
return errors.Wrap(err, "container.ID") return errors.Wrap(err, "container.ID")

View File

@ -21,6 +21,7 @@ import (
"encoding/binary" "encoding/binary"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
@ -221,9 +222,8 @@ func (cs *contentStore) Delete(ctx context.Context, dgst digest.Digest) error {
} }
// Mark content store as dirty for triggering garbage collection // Mark content store as dirty for triggering garbage collection
cs.db.dirtyL.Lock() atomic.AddUint32(&cs.db.dirty, 1)
cs.db.dirtyCS = true cs.db.dirtyCS = true
cs.db.dirtyL.Unlock()
return nil return nil
}) })

View File

@ -21,6 +21,7 @@ import (
"encoding/binary" "encoding/binary"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
@ -75,10 +76,16 @@ type DB struct {
// sweep phases without preventing read transactions. // sweep phases without preventing read transactions.
wlock sync.RWMutex wlock sync.RWMutex
// dirty flags and lock keeps track of datastores which have had deletions // dirty flag indicates that references have been removed which require
// since the last garbage collection. These datastores will will be garbage // a garbage collection to ensure the database is clean. This tracks
// collected during the next garbage collection. // the number of dirty operations. This should be updated and read
dirtyL sync.Mutex // atomically if outside of wlock.Lock.
dirty uint32
// dirtySS and dirtyCS flags keeps track of datastores which have had
// deletions since the last garbage collection. These datastores will
// be garbage collected during the next garbage collection. These
// should only be updated inside of a write transaction or wlock.Lock.
dirtySS map[string]struct{} dirtySS map[string]struct{}
dirtyCS bool dirtyCS bool
@ -162,7 +169,7 @@ func (m *DB) Init(ctx context.Context) error {
} }
} }
// Previous version fo database found // Previous version of database found
if schema != "v0" { if schema != "v0" {
updates := migrations[i:] updates := migrations[i:]
@ -237,12 +244,10 @@ func (m *DB) Update(fn func(*bolt.Tx) error) error {
defer m.wlock.RUnlock() defer m.wlock.RUnlock()
err := m.db.Update(fn) err := m.db.Update(fn)
if err == nil { if err == nil {
m.dirtyL.Lock() dirty := atomic.LoadUint32(&m.dirty) > 0
dirty := m.dirtyCS || len(m.dirtySS) > 0
for _, fn := range m.mutationCallbacks { for _, fn := range m.mutationCallbacks {
fn(dirty) fn(dirty)
} }
m.dirtyL.Unlock()
} }
return err return err
@ -254,9 +259,9 @@ func (m *DB) Update(fn func(*bolt.Tx) error) error {
// The callback function is an argument for whether a deletion has occurred // The callback function is an argument for whether a deletion has occurred
// since the last garbage collection. // since the last garbage collection.
func (m *DB) RegisterMutationCallback(fn func(bool)) { func (m *DB) RegisterMutationCallback(fn func(bool)) {
m.dirtyL.Lock() m.wlock.Lock()
m.mutationCallbacks = append(m.mutationCallbacks, fn) m.mutationCallbacks = append(m.mutationCallbacks, fn)
m.dirtyL.Unlock() m.wlock.Unlock()
} }
// GCStats holds the duration for the different phases of the garbage collector // GCStats holds the duration for the different phases of the garbage collector
@ -282,8 +287,6 @@ func (m *DB) GarbageCollect(ctx context.Context) (gc.Stats, error) {
return nil, err return nil, err
} }
m.dirtyL.Lock()
if err := m.db.Update(func(tx *bolt.Tx) error { if err := m.db.Update(func(tx *bolt.Tx) error {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@ -309,7 +312,6 @@ func (m *DB) GarbageCollect(ctx context.Context) (gc.Stats, error) {
return nil return nil
}); err != nil { }); err != nil {
m.dirtyL.Unlock()
m.wlock.Unlock() m.wlock.Unlock()
return nil, err return nil, err
} }
@ -317,6 +319,9 @@ func (m *DB) GarbageCollect(ctx context.Context) (gc.Stats, error) {
var stats GCStats var stats GCStats
var wg sync.WaitGroup var wg sync.WaitGroup
// reset dirty, no need for atomic inside of wlock.Lock
m.dirty = 0
if len(m.dirtySS) > 0 { if len(m.dirtySS) > 0 {
var sl sync.Mutex var sl sync.Mutex
stats.SnapshotD = map[string]time.Duration{} stats.SnapshotD = map[string]time.Duration{}
@ -349,8 +354,6 @@ func (m *DB) GarbageCollect(ctx context.Context) (gc.Stats, error) {
m.dirtyCS = false m.dirtyCS = false
} }
m.dirtyL.Unlock()
stats.MetaD = time.Since(t1) stats.MetaD = time.Since(t1)
m.wlock.Unlock() m.wlock.Unlock()

View File

@ -21,6 +21,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"strings" "strings"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -249,19 +250,16 @@ func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.Del
return errors.Wrapf(errdefs.ErrNotFound, "image %q", name) return errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
} }
err = bkt.DeleteBucket([]byte(name)) if err = bkt.DeleteBucket([]byte(name)); err != nil {
if err == bolt.ErrBucketNotFound { if err == bolt.ErrBucketNotFound {
return errors.Wrapf(errdefs.ErrNotFound, "image %q", name) err = errors.Wrapf(errdefs.ErrNotFound, "image %q", name)
}
return err
} }
// A reference to a piece of content has been removed, atomic.AddUint32(&s.db.dirty, 1)
// mark content store as dirty for triggering garbage
// collection
s.db.dirtyL.Lock()
s.db.dirtyCS = true
s.db.dirtyL.Unlock()
return err return nil
}) })
} }

View File

@ -20,6 +20,7 @@ import (
"context" "context"
"fmt" "fmt"
"strings" "strings"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -35,14 +36,14 @@ import (
// LeaseManager manages the create/delete lifecycle of leases // LeaseManager manages the create/delete lifecycle of leases
// and also returns existing leases // and also returns existing leases
type LeaseManager struct { type LeaseManager struct {
tx *bolt.Tx db *DB
} }
// NewLeaseManager creates a new lease manager for managing leases using // NewLeaseManager creates a new lease manager for managing leases using
// the provided database transaction. // the provided database transaction.
func NewLeaseManager(tx *bolt.Tx) *LeaseManager { func NewLeaseManager(db *DB) *LeaseManager {
return &LeaseManager{ return &LeaseManager{
tx: tx, db: db,
} }
} }
@ -63,9 +64,10 @@ func (lm *LeaseManager) Create(ctx context.Context, opts ...leases.Opt) (leases.
return leases.Lease{}, err return leases.Lease{}, err
} }
topbkt, err := createBucketIfNotExists(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) if err := update(ctx, lm.db, func(tx *bolt.Tx) error {
topbkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases)
if err != nil { if err != nil {
return leases.Lease{}, err return err
} }
txbkt, err := topbkt.CreateBucket([]byte(l.ID)) txbkt, err := topbkt.CreateBucket([]byte(l.ID))
@ -73,25 +75,29 @@ func (lm *LeaseManager) Create(ctx context.Context, opts ...leases.Opt) (leases.
if err == bolt.ErrBucketExists { if err == bolt.ErrBucketExists {
err = errdefs.ErrAlreadyExists err = errdefs.ErrAlreadyExists
} }
return leases.Lease{}, errors.Wrapf(err, "lease %q", l.ID) return errors.Wrapf(err, "lease %q", l.ID)
} }
t := time.Now().UTC() t := time.Now().UTC()
createdAt, err := t.MarshalBinary() createdAt, err := t.MarshalBinary()
if err != nil { if err != nil {
return leases.Lease{}, err return err
} }
if err := txbkt.Put(bucketKeyCreatedAt, createdAt); err != nil { if err := txbkt.Put(bucketKeyCreatedAt, createdAt); err != nil {
return leases.Lease{}, err return err
} }
if l.Labels != nil { if l.Labels != nil {
if err := boltutil.WriteLabels(txbkt, l.Labels); err != nil { if err := boltutil.WriteLabels(txbkt, l.Labels); err != nil {
return leases.Lease{}, err return err
} }
} }
l.CreatedAt = t l.CreatedAt = t
return nil
}); err != nil {
return leases.Lease{}, err
}
return l, nil return l, nil
} }
@ -102,7 +108,8 @@ func (lm *LeaseManager) Delete(ctx context.Context, lease leases.Lease, _ ...lea
return err return err
} }
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) return update(ctx, lm.db, func(tx *bolt.Tx) error {
topbkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases)
if topbkt == nil { if topbkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID) return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
} }
@ -112,7 +119,11 @@ func (lm *LeaseManager) Delete(ctx context.Context, lease leases.Lease, _ ...lea
} }
return err return err
} }
atomic.AddUint32(&lm.db.dirty, 1)
return nil return nil
})
} }
// List lists all active leases // List lists all active leases
@ -129,12 +140,13 @@ func (lm *LeaseManager) List(ctx context.Context, fs ...string) ([]leases.Lease,
var ll []leases.Lease var ll []leases.Lease
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases) if err := view(ctx, lm.db, func(tx *bolt.Tx) error {
topbkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases)
if topbkt == nil { if topbkt == nil {
return ll, nil return nil
} }
if err := topbkt.ForEach(func(k, v []byte) error { return topbkt.ForEach(func(k, v []byte) error {
if v != nil { if v != nil {
return nil return nil
} }
@ -162,6 +174,7 @@ func (lm *LeaseManager) List(ctx context.Context, fs ...string) ([]leases.Lease,
} }
return nil return nil
})
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
@ -176,7 +189,8 @@ func (lm *LeaseManager) AddResource(ctx context.Context, lease leases.Lease, r l
return err return err
} }
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID)) return update(ctx, lm.db, func(tx *bolt.Tx) error {
topbkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID))
if topbkt == nil { if topbkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID) return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
} }
@ -194,6 +208,7 @@ func (lm *LeaseManager) AddResource(ctx context.Context, lease leases.Lease, r l
} }
} }
return bkt.Put([]byte(ref), nil) return bkt.Put([]byte(ref), nil)
})
} }
// DeleteResource dereferences the resource by the provided lease. // DeleteResource dereferences the resource by the provided lease.
@ -203,7 +218,8 @@ func (lm *LeaseManager) DeleteResource(ctx context.Context, lease leases.Lease,
return err return err
} }
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID)) return update(ctx, lm.db, func(tx *bolt.Tx) error {
topbkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID))
if topbkt == nil { if topbkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID) return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
} }
@ -221,10 +237,16 @@ func (lm *LeaseManager) DeleteResource(ctx context.Context, lease leases.Lease,
bkt = bkt.Bucket([]byte(key)) bkt = bkt.Bucket([]byte(key))
} }
if bkt == nil { if bkt != nil {
return nil if err := bkt.Delete([]byte(ref)); err != nil {
return err
} }
return bkt.Delete([]byte(ref)) }
atomic.AddUint32(&lm.db.dirty, 1)
return nil
})
} }
// ListResources lists all the resources referenced by the lease. // ListResources lists all the resources referenced by the lease.
@ -234,12 +256,14 @@ func (lm *LeaseManager) ListResources(ctx context.Context, lease leases.Lease) (
return nil, err return nil, err
} }
topbkt := getBucket(lm.tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID)) var rs []leases.Resource
if topbkt == nil {
return nil, errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
}
rs := make([]leases.Resource, 0) if err := view(ctx, lm.db, func(tx *bolt.Tx) error {
topbkt := getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectLeases, []byte(lease.ID))
if topbkt == nil {
return errors.Wrapf(errdefs.ErrNotFound, "lease %q", lease.ID)
}
// content resources // content resources
if cbkt := topbkt.Bucket(bucketKeyObjectContent); cbkt != nil { if cbkt := topbkt.Bucket(bucketKeyObjectContent); cbkt != nil {
@ -251,7 +275,7 @@ func (lm *LeaseManager) ListResources(ctx context.Context, lease leases.Lease) (
return nil return nil
}); err != nil { }); err != nil {
return nil, err return err
} }
} }
@ -265,7 +289,7 @@ func (lm *LeaseManager) ListResources(ctx context.Context, lease leases.Lease) (
return nil return nil
}); err != nil { }); err != nil {
return nil, err return err
} }
} }
@ -285,9 +309,14 @@ func (lm *LeaseManager) ListResources(ctx context.Context, lease leases.Lease) (
return nil return nil
}) })
}); err != nil { }); err != nil {
return nil, err return err
} }
} }
return nil
}); err != nil {
return nil, err
}
return rs, nil return rs, nil
} }

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -517,9 +518,8 @@ func (s *snapshotter) Remove(ctx context.Context, key string) error {
} }
// Mark snapshotter as dirty for triggering garbage collection // Mark snapshotter as dirty for triggering garbage collection
s.db.dirtyL.Lock() atomic.AddUint32(&s.db.dirty, 1)
s.db.dirtySS[s.name] = struct{}{} s.db.dirtySS[s.name] = struct{}{}
s.db.dirtyL.Unlock()
return nil return nil
}) })

View File

@ -18,27 +18,29 @@
package v1 package v1
import "github.com/containerd/cgroups" import (
v1 "github.com/containerd/cgroups/stats/v1"
)
type ( type (
// Metrics alias // Metrics alias
Metrics = cgroups.Metrics Metrics = v1.Metrics
// BlkIOEntry alias // BlkIOEntry alias
BlkIOEntry = cgroups.BlkIOEntry BlkIOEntry = v1.BlkIOEntry
// MemoryStat alias // MemoryStat alias
MemoryStat = cgroups.MemoryStat MemoryStat = v1.MemoryStat
// CPUStat alias // CPUStat alias
CPUStat = cgroups.CPUStat CPUStat = v1.CPUStat
// CPUUsage alias // CPUUsage alias
CPUUsage = cgroups.CPUUsage CPUUsage = v1.CPUUsage
// BlkIOStat alias // BlkIOStat alias
BlkIOStat = cgroups.BlkIOStat BlkIOStat = v1.BlkIOStat
// PidsStat alias // PidsStat alias
PidsStat = cgroups.PidsStat PidsStat = v1.PidsStat
// RdmaStat alias // RdmaStat alias
RdmaStat = cgroups.RdmaStat RdmaStat = v1.RdmaStat
// RdmaEntry alias // RdmaEntry alias
RdmaEntry = cgroups.RdmaEntry RdmaEntry = v1.RdmaEntry
// HugetlbStat alias // HugetlbStat alias
HugetlbStat = cgroups.HugetlbStat HugetlbStat = v1.HugetlbStat
) )

View File

@ -64,7 +64,7 @@ func Namespace(ctx context.Context) (string, bool) {
return namespace, ok return namespace, ok
} }
// NamespaceRequired returns the valid namepace from the context or an error. // NamespaceRequired returns the valid namespace from the context or an error.
func NamespaceRequired(ctx context.Context) (string, error) { func NamespaceRequired(ctx context.Context) (string, error) {
namespace, ok := Namespace(ctx) namespace, ok := Namespace(ctx)
if !ok || namespace == "" { if !ok || namespace == "" {

View File

@ -78,7 +78,7 @@ func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s
return err return err
} }
// ApplyOpts applys the options to the given spec, injecting data from the // ApplyOpts applies the options to the given spec, injecting data from the
// context, client and container instance. // context, client and container instance.
func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *Spec, opts ...SpecOpts) error { func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *Spec, opts ...SpecOpts) error {
for _, o := range opts { for _, o := range opts {

View File

@ -69,3 +69,7 @@ func (s *deletedState) SetExited(status int) {
func (s *deletedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) { func (s *deletedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
return nil, errors.Errorf("cannot exec in a deleted state") return nil, errors.Errorf("cannot exec in a deleted state")
} }
func (s *deletedState) Status(ctx context.Context) (string, error) {
return "stopped", nil
}

View File

@ -261,17 +261,5 @@ func (e *execProcess) Status(ctx context.Context) (string, error) {
} }
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()
// if we don't have a pid(pid=0) then the exec process has just been created return e.execState.Status(ctx)
if e.pid.get() == 0 {
return "created", nil
}
if e.pid.get() == StoppedPID {
return "stopped", nil
}
// if we have a pid and it can be signaled, the process is running
if err := unix.Kill(e.pid.get(), 0); err == nil {
return "running", nil
}
// else if we have a pid but it can nolonger be signaled, it has stopped
return "stopped", nil
} }

View File

@ -31,6 +31,7 @@ type execState interface {
Delete(context.Context) error Delete(context.Context) error
Kill(context.Context, uint32, bool) error Kill(context.Context, uint32, bool) error
SetExited(int) SetExited(int)
Status(context.Context) (string, error)
} }
type execCreatedState struct { type execCreatedState struct {
@ -82,6 +83,10 @@ func (s *execCreatedState) SetExited(status int) {
} }
} }
func (s *execCreatedState) Status(ctx context.Context) (string, error) {
return "created", nil
}
type execRunningState struct { type execRunningState struct {
p *execProcess p *execProcess
} }
@ -120,6 +125,10 @@ func (s *execRunningState) SetExited(status int) {
} }
} }
func (s *execRunningState) Status(ctx context.Context) (string, error) {
return "running", nil
}
type execStoppedState struct { type execStoppedState struct {
p *execProcess p *execProcess
} }
@ -157,3 +166,7 @@ func (s *execStoppedState) Kill(ctx context.Context, sig uint32, all bool) error
func (s *execStoppedState) SetExited(status int) { func (s *execStoppedState) SetExited(status int) {
// no op // no op
} }
func (s *execStoppedState) Status(ctx context.Context) (string, error) {
return "stopped", nil
}

View File

@ -62,6 +62,8 @@ type Init struct {
Platform stdio.Platform Platform stdio.Platform
io *processIO io *processIO
runtime *runc.Runc runtime *runc.Runc
// pausing preserves the pausing state.
pausing *atomicBool
status int status int
exited time.Time exited time.Time
pid safePid pid safePid
@ -97,6 +99,7 @@ func New(id string, runtime *runc.Runc, stdio stdio.Stdio) *Init {
p := &Init{ p := &Init{
id: id, id: id,
runtime: runtime, runtime: runtime,
pausing: new(atomicBool),
stdio: stdio, stdio: stdio,
status: 0, status: 0,
waitBlock: make(chan struct{}), waitBlock: make(chan struct{}),
@ -237,17 +240,14 @@ func (p *Init) ExitedAt() time.Time {
// Status of the process // Status of the process
func (p *Init) Status(ctx context.Context) (string, error) { func (p *Init) Status(ctx context.Context) (string, error) {
if p.pausing.get() {
return "pausing", nil
}
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
c, err := p.runtime.State(ctx, p.id) return p.initState.Status(ctx)
if err != nil {
if strings.Contains(err.Error(), "does not exist") {
return "stopped", nil
}
return "", p.runtimeError(err, "OCI runtime state failed")
}
return c.Status, nil
} }
// Start the init process // Start the init process

View File

@ -37,6 +37,7 @@ type initState interface {
Exec(context.Context, string, *ExecConfig) (Process, error) Exec(context.Context, string, *ExecConfig) (Process, error)
Kill(context.Context, uint32, bool) error Kill(context.Context, uint32, bool) error
SetExited(int) SetExited(int)
Status(context.Context) (string, error)
} }
type createdState struct { type createdState struct {
@ -103,6 +104,10 @@ func (s *createdState) Exec(ctx context.Context, path string, r *ExecConfig) (Pr
return s.p.exec(ctx, path, r) return s.p.exec(ctx, path, r)
} }
func (s *createdState) Status(ctx context.Context) (string, error) {
return "created", nil
}
type createdCheckpointState struct { type createdCheckpointState struct {
p *Init p *Init
opts *runc.RestoreOpts opts *runc.RestoreOpts
@ -211,6 +216,10 @@ func (s *createdCheckpointState) Exec(ctx context.Context, path string, r *ExecC
return nil, errors.Errorf("cannot exec in a created state") return nil, errors.Errorf("cannot exec in a created state")
} }
func (s *createdCheckpointState) Status(ctx context.Context) (string, error) {
return "created", nil
}
type runningState struct { type runningState struct {
p *Init p *Init
} }
@ -228,6 +237,13 @@ func (s *runningState) transition(name string) error {
} }
func (s *runningState) Pause(ctx context.Context) error { func (s *runningState) Pause(ctx context.Context) error {
s.p.pausing.set(true)
// NOTE "pausing" will be returned in the short window
// after `transition("paused")`, before `pausing` is reset
// to false. That doesn't break the state machine, just
// delays the "paused" state a little bit.
defer s.p.pausing.set(false)
if err := s.p.runtime.Pause(ctx, s.p.id); err != nil { if err := s.p.runtime.Pause(ctx, s.p.id); err != nil {
return s.p.runtimeError(err, "OCI runtime pause failed") return s.p.runtimeError(err, "OCI runtime pause failed")
} }
@ -271,6 +287,10 @@ func (s *runningState) Exec(ctx context.Context, path string, r *ExecConfig) (Pr
return s.p.exec(ctx, path, r) return s.p.exec(ctx, path, r)
} }
func (s *runningState) Status(ctx context.Context) (string, error) {
return "running", nil
}
type pausedState struct { type pausedState struct {
p *Init p *Init
} }
@ -335,6 +355,10 @@ func (s *pausedState) Exec(ctx context.Context, path string, r *ExecConfig) (Pro
return nil, errors.Errorf("cannot exec in a paused state") return nil, errors.Errorf("cannot exec in a paused state")
} }
func (s *pausedState) Status(ctx context.Context) (string, error) {
return "paused", nil
}
type stoppedState struct { type stoppedState struct {
p *Init p *Init
} }
@ -387,3 +411,7 @@ func (s *stoppedState) SetExited(status int) {
func (s *stoppedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) { func (s *stoppedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
return nil, errors.Errorf("cannot exec in a stopped state") return nil, errors.Errorf("cannot exec in a stopped state")
} }
func (s *stoppedState) Status(ctx context.Context) (string, error) {
return "stopped", nil
}

View File

@ -27,6 +27,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -62,6 +63,20 @@ func (s *safePid) set(pid int) {
s.Unlock() s.Unlock()
} }
type atomicBool int32
func (ab *atomicBool) set(b bool) {
if b {
atomic.StoreInt32((*int32)(ab), 1)
} else {
atomic.StoreInt32((*int32)(ab), 0)
}
}
func (ab *atomicBool) get() bool {
return atomic.LoadInt32((*int32)(ab)) == 1
}
// TODO(mlaventure): move to runc package? // TODO(mlaventure): move to runc package?
func getLastRuntimeError(r *runc.Runc) (string, error) { func getLastRuntimeError(r *runc.Runc) (string, error) {
if r.Log == "" { if r.Log == "" {
@ -127,6 +142,7 @@ func checkKillError(err error) error {
} }
if strings.Contains(err.Error(), "os: process already finished") || if strings.Contains(err.Error(), "os: process already finished") ||
strings.Contains(err.Error(), "container not running") || strings.Contains(err.Error(), "container not running") ||
strings.Contains(strings.ToLower(err.Error()), "no such process") ||
err == unix.ESRCH { err == unix.ESRCH {
return errors.Wrapf(errdefs.ErrNotFound, "process already finished") return errors.Wrapf(errdefs.ErrNotFound, "process already finished")
} }

View File

@ -61,7 +61,7 @@ func NewExitStatus(code uint32, t time.Time, err error) *ExitStatus {
} }
} }
// ExitStatus encapsulates a process' exit status. // ExitStatus encapsulates a process's exit status.
// It is used by `Wait()` to return either a process exit code or an error // It is used by `Wait()` to return either a process exit code or an error
type ExitStatus struct { type ExitStatus struct {
code uint32 code uint32

View File

@ -0,0 +1,797 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package docker provides a general type to represent any way of referencing images within the registry.
// Its main purpose is to abstract tags and digests (content-addressable hash).
//
// Grammar
//
// reference := name [ ":" tag ] [ "@" digest ]
// name := [domain '/'] path-component ['/' path-component]*
// domain := domain-component ['.' domain-component]* [':' port-number]
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// path-component := alpha-numeric [separator alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/
//
// tag := /[\w][\w.-]{0,127}/
//
// digest := digest-algorithm ":" digest-hex
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
// digest-algorithm-separator := /[+.-_]/
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
//
// identifier := /[a-f0-9]{64}/
// short-identifier := /[a-f0-9]{6,64}/
package docker
import (
"errors"
"fmt"
"path"
"regexp"
"strings"
"github.com/opencontainers/go-digest"
)
const (
// NameTotalLengthMax is the maximum total number of characters in a repository name.
NameTotalLengthMax = 255
)
var (
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
ErrReferenceInvalidFormat = errors.New("invalid reference format")
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
ErrTagInvalidFormat = errors.New("invalid tag format")
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
ErrDigestInvalidFormat = errors.New("invalid digest format")
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
// ErrNameEmpty is returned for empty, invalid repository names.
ErrNameEmpty = errors.New("repository name must have at least one component")
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
// ErrNameNotCanonical is returned when a name is not canonical.
ErrNameNotCanonical = errors.New("repository name must be canonical")
)
// Reference is an opaque object reference identifier that may include
// modifiers such as a hostname, name, tag, and digest.
type Reference interface {
// String returns the full reference
String() string
}
// Field provides a wrapper type for resolving correct reference types when
// working with encoding.
type Field struct {
reference Reference
}
// AsField wraps a reference in a Field for encoding.
func AsField(reference Reference) Field {
return Field{reference}
}
// Reference unwraps the reference type from the field to
// return the Reference object. This object should be
// of the appropriate type to further check for different
// reference types.
func (f Field) Reference() Reference {
return f.reference
}
// MarshalText serializes the field to byte text which
// is the string of the reference.
func (f Field) MarshalText() (p []byte, err error) {
return []byte(f.reference.String()), nil
}
// UnmarshalText parses text bytes by invoking the
// reference parser to ensure the appropriately
// typed reference object is wrapped by field.
func (f *Field) UnmarshalText(p []byte) error {
r, err := Parse(string(p))
if err != nil {
return err
}
f.reference = r
return nil
}
// Named is an object with a full name
type Named interface {
Reference
Name() string
}
// Tagged is an object which has a tag
type Tagged interface {
Reference
Tag() string
}
// NamedTagged is an object including a name and tag.
type NamedTagged interface {
Named
Tag() string
}
// Digested is an object which has a digest
// in which it can be referenced by
type Digested interface {
Reference
Digest() digest.Digest
}
// Canonical reference is an object with a fully unique
// name including a name with domain and digest
type Canonical interface {
Named
Digest() digest.Digest
}
// namedRepository is a reference to a repository with a name.
// A namedRepository has both domain and path components.
type namedRepository interface {
Named
Domain() string
Path() string
}
// Domain returns the domain part of the Named reference
func Domain(named Named) string {
if r, ok := named.(namedRepository); ok {
return r.Domain()
}
domain, _ := splitDomain(named.Name())
return domain
}
// Path returns the name without the domain part of the Named reference
func Path(named Named) (name string) {
if r, ok := named.(namedRepository); ok {
return r.Path()
}
_, path := splitDomain(named.Name())
return path
}
func splitDomain(name string) (string, string) {
match := anchoredNameRegexp.FindStringSubmatch(name)
if len(match) != 3 {
return "", name
}
return match[1], match[2]
}
// SplitHostname splits a named reference into a
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
// DEPRECATED: Use Domain or Path
func SplitHostname(named Named) (string, string) {
if r, ok := named.(namedRepository); ok {
return r.Domain(), r.Path()
}
return splitDomain(named.Name())
}
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {
matches := ReferenceRegexp.FindStringSubmatch(s)
if matches == nil {
if s == "" {
return nil, ErrNameEmpty
}
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
return nil, ErrNameContainsUppercase
}
return nil, ErrReferenceInvalidFormat
}
if len(matches[1]) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
var repo repository
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
if len(nameMatch) == 3 {
repo.domain = nameMatch[1]
repo.path = nameMatch[2]
} else {
repo.domain = ""
repo.path = matches[1]
}
ref := reference{
namedRepository: repo,
tag: matches[2],
}
if matches[3] != "" {
var err error
ref.digest, err = digest.Parse(matches[3])
if err != nil {
return nil, err
}
}
r := getBestReferenceType(ref)
if r == nil {
return nil, ErrNameEmpty
}
return r, nil
}
// ParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name and be in the canonical
// form, otherwise an error is returned.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
named, err := ParseNormalizedNamed(s)
if err != nil {
return nil, err
}
if named.String() != s {
return nil, ErrNameNotCanonical
}
return named, nil
}
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
if len(name) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
match := anchoredNameRegexp.FindStringSubmatch(name)
if match == nil || len(match) != 3 {
return nil, ErrReferenceInvalidFormat
}
return repository{
domain: match[1],
path: match[2],
}, nil
}
// WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag.
func WithTag(name Named, tag string) (NamedTagged, error) {
if !anchoredTagRegexp.MatchString(tag) {
return nil, ErrTagInvalidFormat
}
var repo repository
if r, ok := name.(namedRepository); ok {
repo.domain = r.Domain()
repo.path = r.Path()
} else {
repo.path = name.Name()
}
if canonical, ok := name.(Canonical); ok {
return reference{
namedRepository: repo,
tag: tag,
digest: canonical.Digest(),
}, nil
}
return taggedReference{
namedRepository: repo,
tag: tag,
}, nil
}
// WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest.
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
if !anchoredDigestRegexp.MatchString(digest.String()) {
return nil, ErrDigestInvalidFormat
}
var repo repository
if r, ok := name.(namedRepository); ok {
repo.domain = r.Domain()
repo.path = r.Path()
} else {
repo.path = name.Name()
}
if tagged, ok := name.(Tagged); ok {
return reference{
namedRepository: repo,
tag: tagged.Tag(),
digest: digest,
}, nil
}
return canonicalReference{
namedRepository: repo,
digest: digest,
}, nil
}
// TrimNamed removes any tag or digest from the named reference.
func TrimNamed(ref Named) Named {
domain, path := SplitHostname(ref)
return repository{
domain: domain,
path: path,
}
}
func getBestReferenceType(ref reference) Reference {
if ref.Name() == "" {
// Allow digest only references
if ref.digest != "" {
return digestReference(ref.digest)
}
return nil
}
if ref.tag == "" {
if ref.digest != "" {
return canonicalReference{
namedRepository: ref.namedRepository,
digest: ref.digest,
}
}
return ref.namedRepository
}
if ref.digest == "" {
return taggedReference{
namedRepository: ref.namedRepository,
tag: ref.tag,
}
}
return ref
}
type reference struct {
namedRepository
tag string
digest digest.Digest
}
func (r reference) String() string {
return r.Name() + ":" + r.tag + "@" + r.digest.String()
}
func (r reference) Tag() string {
return r.tag
}
func (r reference) Digest() digest.Digest {
return r.digest
}
type repository struct {
domain string
path string
}
func (r repository) String() string {
return r.Name()
}
func (r repository) Name() string {
if r.domain == "" {
return r.path
}
return r.domain + "/" + r.path
}
func (r repository) Domain() string {
return r.domain
}
func (r repository) Path() string {
return r.path
}
type digestReference digest.Digest
func (d digestReference) String() string {
return digest.Digest(d).String()
}
func (d digestReference) Digest() digest.Digest {
return digest.Digest(d)
}
type taggedReference struct {
namedRepository
tag string
}
func (t taggedReference) String() string {
return t.Name() + ":" + t.tag
}
func (t taggedReference) Tag() string {
return t.tag
}
type canonicalReference struct {
namedRepository
digest digest.Digest
}
func (c canonicalReference) String() string {
return c.Name() + "@" + c.digest.String()
}
func (c canonicalReference) Digest() digest.Digest {
return c.digest
}
var (
// alphaNumericRegexp defines the alpha numeric atom, typically a
// component of names. This only allows lower case characters and digits.
alphaNumericRegexp = match(`[a-z0-9]+`)
// separatorRegexp defines the separators allowed to be embedded in name
// components. This allow one period, one or two underscore and multiple
// dashes.
separatorRegexp = match(`(?:[._]|__|[-]*)`)
// nameComponentRegexp restricts registry path component names to start
// with at least one letter or number, with following parts able to be
// separated by one period, one or two underscore and multiple dashes.
nameComponentRegexp = expression(
alphaNumericRegexp,
optional(repeated(separatorRegexp, alphaNumericRegexp)))
// domainComponentRegexp restricts the registry domain component of a
// repository name to start with a component as defined by DomainRegexp
// and followed by an optional port.
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// DomainRegexp defines the structure of potential domain components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names.
DomainRegexp = expression(
domainComponentRegexp,
optional(repeated(literal(`.`), domainComponentRegexp)),
optional(literal(`:`), match(`[0-9]+`)))
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
TagRegexp = match(`[\w][\w.-]{0,127}`)
// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = anchored(TagRegexp)
// DigestRegexp matches valid digests.
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = anchored(DigestRegexp)
// NameRegexp is the format for the name component of references. The
// regexp has capturing groups for the domain and name part omitting
// the separating forward slash from either.
NameRegexp = expression(
optional(DomainRegexp, literal(`/`)),
nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp)))
// anchoredNameRegexp is used to parse a name value, capturing the
// domain and trailing components.
anchoredNameRegexp = anchored(
optional(capture(DomainRegexp), literal(`/`)),
capture(nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp))))
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
ReferenceRegexp = anchored(capture(NameRegexp),
optional(literal(":"), capture(TagRegexp)),
optional(literal("@"), capture(DigestRegexp)))
// IdentifierRegexp is the format for string identifier used as a
// content addressable identifier using sha256. These identifiers
// are like digests without the algorithm, since sha256 is used.
IdentifierRegexp = match(`([a-f0-9]{64})`)
// ShortIdentifierRegexp is the format used to represent a prefix
// of an identifier. A prefix may be used to match a sha256 identifier
// within a list of trusted identifiers.
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
// anchoredIdentifierRegexp is used to check or match an
// identifier value, anchored at start and end of string.
anchoredIdentifierRegexp = anchored(IdentifierRegexp)
)
// match compiles the string to a regular expression.
var match = regexp.MustCompile
// literal compiles s into a literal regular expression, escaping any regexp
// reserved characters.
func literal(s string) *regexp.Regexp {
re := match(regexp.QuoteMeta(s))
if _, complete := re.LiteralPrefix(); !complete {
panic("must be a literal")
}
return re
}
// expression defines a full expression, where each regular expression must
// follow the previous.
func expression(res ...*regexp.Regexp) *regexp.Regexp {
var s string
for _, re := range res {
s += re.String()
}
return match(s)
}
// optional wraps the expression in a non-capturing group and makes the
// production optional.
func optional(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `?`)
}
// repeated wraps the regexp in a non-capturing group to get one or more
// matches.
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `+`)
}
// group wraps the regexp in a non-capturing group.
func group(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(?:` + expression(res...).String() + `)`)
}
// capture wraps the expression in a capturing group.
func capture(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(` + expression(res...).String() + `)`)
}
// anchored anchors the regular expression by adding start and end delimiters.
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
return match(`^` + expression(res...).String() + `$`)
}
var (
legacyDefaultDomain = "index.docker.io"
defaultDomain = "docker.io"
officialRepoName = "library"
defaultTag = "latest"
)
// normalizedNamed represents a name which has been
// normalized and has a familiar form. A familiar name
// is what is used in Docker UI. An example normalized
// name is "docker.io/library/ubuntu" and corresponding
// familiar name of "ubuntu".
type normalizedNamed interface {
Named
Familiar() Named
}
// ParseNormalizedNamed parses a string into a named reference
// transforming a familiar name from Docker UI to a fully
// qualified reference. If the value may be an identifier
// use ParseAnyReference.
func ParseNormalizedNamed(s string) (Named, error) {
if ok := anchoredIdentifierRegexp.MatchString(s); ok {
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
}
domain, remainder := splitDockerDomain(s)
var remoteName string
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
remoteName = remainder[:tagSep]
} else {
remoteName = remainder
}
if strings.ToLower(remoteName) != remoteName {
return nil, errors.New("invalid reference format: repository name must be lowercase")
}
ref, err := Parse(domain + "/" + remainder)
if err != nil {
return nil, err
}
named, isNamed := ref.(Named)
if !isNamed {
return nil, fmt.Errorf("reference %s has no name", ref.String())
}
return named, nil
}
// ParseDockerRef normalizes the image reference following the docker convention. This is added
// mainly for backward compatibility.
// The reference returned can only be either tagged or digested. For reference contains both tag
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
func ParseDockerRef(ref string) (Named, error) {
named, err := ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, ok := named.(NamedTagged); ok {
if canonical, ok := named.(Canonical); ok {
// The reference is both tagged and digested, only
// return digested.
newNamed, err := WithName(canonical.Name())
if err != nil {
return nil, err
}
newCanonical, err := WithDigest(newNamed, canonical.Digest())
if err != nil {
return nil, err
}
return newCanonical, nil
}
}
return TagNameOnly(named), nil
}
// splitDockerDomain splits a repository name to domain and remotename string.
// If no valid domain is found, the default domain is used. Repository name
// needs to be already validated before.
func splitDockerDomain(name string) (domain, remainder string) {
i := strings.IndexRune(name, '/')
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
domain, remainder = defaultDomain, name
} else {
domain, remainder = name[:i], name[i+1:]
}
if domain == legacyDefaultDomain {
domain = defaultDomain
}
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
remainder = officialRepoName + "/" + remainder
}
return
}
// familiarizeName returns a shortened version of the name familiar
// to to the Docker UI. Familiar names have the default domain
// "docker.io" and "library/" repository prefix removed.
// For example, "docker.io/library/redis" will have the familiar
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
// Returns a familiarized named only reference.
func familiarizeName(named namedRepository) repository {
repo := repository{
domain: named.Domain(),
path: named.Path(),
}
if repo.domain == defaultDomain {
repo.domain = ""
// Handle official repositories which have the pattern "library/<official repo name>"
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
repo.path = split[1]
}
}
return repo
}
func (r reference) Familiar() Named {
return reference{
namedRepository: familiarizeName(r.namedRepository),
tag: r.tag,
digest: r.digest,
}
}
func (r repository) Familiar() Named {
return familiarizeName(r)
}
func (t taggedReference) Familiar() Named {
return taggedReference{
namedRepository: familiarizeName(t.namedRepository),
tag: t.tag,
}
}
func (c canonicalReference) Familiar() Named {
return canonicalReference{
namedRepository: familiarizeName(c.namedRepository),
digest: c.digest,
}
}
// TagNameOnly adds the default tag "latest" to a reference if it only has
// a repo name.
func TagNameOnly(ref Named) Named {
if IsNameOnly(ref) {
namedTagged, err := WithTag(ref, defaultTag)
if err != nil {
// Default tag must be valid, to create a NamedTagged
// type with non-validated input the WithTag function
// should be used instead
panic(err)
}
return namedTagged
}
return ref
}
// ParseAnyReference parses a reference string as a possible identifier,
// full digest, or familiar name.
func ParseAnyReference(ref string) (Reference, error) {
if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
return digestReference("sha256:" + ref), nil
}
if dgst, err := digest.Parse(ref); err == nil {
return digestReference(dgst), nil
}
return ParseNormalizedNamed(ref)
}
// IsNameOnly returns true if reference only contains a repo name.
func IsNameOnly(ref Named) bool {
if _, ok := ref.(NamedTagged); ok {
return false
}
if _, ok := ref.(Canonical); ok {
return false
}
return true
}
// FamiliarName returns the familiar name string
// for the given named, familiarizing if needed.
func FamiliarName(ref Named) string {
if nn, ok := ref.(normalizedNamed); ok {
return nn.Familiar().Name()
}
return ref.Name()
}
// FamiliarString returns the familiar string representation
// for the given reference, familiarizing if needed.
func FamiliarString(ref Reference) string {
if nn, ok := ref.(normalizedNamed); ok {
return nn.Familiar().String()
}
return ref.String()
}
// FamiliarMatch reports whether ref matches the specified pattern.
// See https://godoc.org/path#Match for supported patterns.
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
matched, err := path.Match(pattern, FamiliarString(ref))
if namedRef, isNamed := ref.(Named); isNamed && !matched {
matched, _ = path.Match(pattern, FamiliarName(namedRef))
}
return matched, err
}

View File

@ -1,4 +1,20 @@
package errcode /*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import ( import (
"encoding/json" "encoding/json"

View File

@ -1,4 +1,20 @@
package errcode /*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package docker
import ( import (
"fmt" "fmt"

View File

@ -29,7 +29,6 @@ import (
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/docker/distribution/registry/api/errcode"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -136,7 +135,7 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
} }
func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string, offset int64) (io.ReadCloser, error) { func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string, offset int64) (io.ReadCloser, error) {
req.header.Set("Accept", strings.Join([]string{mediatype, `*`}, ", ")) req.header.Set("Accept", strings.Join([]string{mediatype, `*/*`}, ", "))
if offset > 0 { if offset > 0 {
// Note: "Accept-Ranges: bytes" cannot be trusted as some endpoints // Note: "Accept-Ranges: bytes" cannot be trusted as some endpoints
@ -160,7 +159,7 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
if resp.StatusCode == http.StatusNotFound { if resp.StatusCode == http.StatusNotFound {
return nil, errors.Wrapf(errdefs.ErrNotFound, "content at %v not found", req.String()) return nil, errors.Wrapf(errdefs.ErrNotFound, "content at %v not found", req.String())
} }
var registryErr errcode.Errors var registryErr Errors
if err := json.NewDecoder(resp.Body).Decode(&registryErr); err != nil || registryErr.Len() < 1 { if err := json.NewDecoder(resp.Body).Decode(&registryErr); err != nil || registryErr.Len() < 1 {
return nil, errors.Errorf("unexpected status code %v: %v", req.String(), resp.Status) return nil, errors.Errorf("unexpected status code %v: %v", req.String(), resp.Status)
} }

View File

@ -80,7 +80,7 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
} }
req := p.request(host, http.MethodHead, existCheck...) req := p.request(host, http.MethodHead, existCheck...)
req.header.Set("Accept", strings.Join([]string{desc.MediaType, `*`}, ", ")) req.header.Set("Accept", strings.Join([]string{desc.MediaType, `*/*`}, ", "))
log.G(ctx).WithField("url", req.String()).Debugf("checking and pushing to") log.G(ctx).WithField("url", req.String()).Debugf("checking and pushing to")

View File

@ -155,7 +155,7 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2Manifest,
images.MediaTypeDockerSchema2ManifestList, images.MediaTypeDockerSchema2ManifestList,
ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageManifest,
ocispec.MediaTypeImageIndex, "*"}, ", ")) ocispec.MediaTypeImageIndex, "*/*"}, ", "))
} else { } else {
resolveHeader["Accept"] = options.Headers["Accept"] resolveHeader["Accept"] = options.Headers["Accept"]
delete(options.Headers, "Accept") delete(options.Headers, "Accept")

View File

@ -50,7 +50,6 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -115,7 +114,7 @@ func New(ic *plugin.InitContext) (interface{}, error) {
root: ic.Root, root: ic.Root,
state: ic.State, state: ic.State,
tasks: runtime.NewTaskList(), tasks: runtime.NewTaskList(),
db: m.(*metadata.DB), containers: metadata.NewContainerStore(m.(*metadata.DB)),
address: ic.Address, address: ic.Address,
events: ic.Events, events: ic.Events,
config: cfg, config: cfg,
@ -139,7 +138,7 @@ type Runtime struct {
address string address string
tasks *runtime.TaskList tasks *runtime.TaskList
db *metadata.DB containers containers.Store
events *exchange.Exchange events *exchange.Exchange
config *Config config *Config
@ -508,14 +507,8 @@ func (r *Runtime) getRuntime(ctx context.Context, ns, id string) (*runc.Runc, er
} }
func (r *Runtime) getRuncOptions(ctx context.Context, id string) (*runctypes.RuncOptions, error) { func (r *Runtime) getRuncOptions(ctx context.Context, id string) (*runctypes.RuncOptions, error) {
var container containers.Container container, err := r.containers.Get(ctx, id)
if err != nil {
if err := r.db.View(func(tx *bolt.Tx) error {
store := metadata.NewContainerStore(tx)
var err error
container, err = store.Get(ctx, id)
return err
}); err != nil {
return nil, err return nil, err
} }

View File

@ -91,9 +91,12 @@ func (t *Task) PID() uint32 {
// Delete the task and return the exit status // Delete the task and return the exit status
func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) { func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
rsp, err := t.shim.Delete(ctx, empty) rsp, shimErr := t.shim.Delete(ctx, empty)
if err != nil && !errdefs.IsNotFound(err) { if shimErr != nil {
return nil, errdefs.FromGRPC(err) shimErr = errdefs.FromGRPC(shimErr)
if !errdefs.IsNotFound(shimErr) {
return nil, shimErr
}
} }
t.tasks.Delete(ctx, t.id) t.tasks.Delete(ctx, t.id)
if err := t.shim.KillShim(ctx); err != nil { if err := t.shim.KillShim(ctx); err != nil {
@ -102,6 +105,9 @@ func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
if err := t.bundle.Delete(); err != nil { if err := t.bundle.Delete(); err != nil {
log.G(ctx).WithError(err).Error("failed to delete bundle") log.G(ctx).WithError(err).Error("failed to delete bundle")
} }
if shimErr != nil {
return nil, shimErr
}
t.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{ t.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
ContainerID: t.id, ContainerID: t.id,
ExitStatus: rsp.ExitStatus, ExitStatus: rsp.ExitStatus,

View File

@ -217,7 +217,7 @@ func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteR
return nil, err return nil, err
} }
if err := p.Delete(ctx); err != nil { if err := p.Delete(ctx); err != nil {
return nil, err return nil, errdefs.ToGRPC(err)
} }
s.mu.Lock() s.mu.Lock()
delete(s.processes, s.id) delete(s.processes, s.id)
@ -240,7 +240,7 @@ func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessReq
return nil, err return nil, err
} }
if err := p.Delete(ctx); err != nil { if err := p.Delete(ctx); err != nil {
return nil, err return nil, errdefs.ToGRPC(err)
} }
s.mu.Lock() s.mu.Lock()
delete(s.processes, r.ID) delete(s.processes, r.ID)

View File

@ -33,7 +33,6 @@ import (
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/runtime" "github.com/containerd/containerd/runtime"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
bolt "go.etcd.io/bbolt"
) )
// Config for the v2 runtime // Config for the v2 runtime
@ -69,13 +68,15 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return New(ic.Context, ic.Root, ic.State, ic.Address, ic.TTRPCAddress, ic.Events, m.(*metadata.DB)) cs := metadata.NewContainerStore(m.(*metadata.DB))
return New(ic.Context, ic.Root, ic.State, ic.Address, ic.TTRPCAddress, ic.Events, cs)
}, },
}) })
} }
// New task manager for v2 shims // New task manager for v2 shims
func New(ctx context.Context, root, state, containerdAddress, containerdTTRPCAddress string, events *exchange.Exchange, db *metadata.DB) (*TaskManager, error) { func New(ctx context.Context, root, state, containerdAddress, containerdTTRPCAddress string, events *exchange.Exchange, cs containers.Store) (*TaskManager, error) {
for _, d := range []string{root, state} { for _, d := range []string{root, state} {
if err := os.MkdirAll(d, 0711); err != nil { if err := os.MkdirAll(d, 0711); err != nil {
return nil, err return nil, err
@ -88,7 +89,7 @@ func New(ctx context.Context, root, state, containerdAddress, containerdTTRPCAdd
containerdTTRPCAddress: containerdTTRPCAddress, containerdTTRPCAddress: containerdTTRPCAddress,
tasks: runtime.NewTaskList(), tasks: runtime.NewTaskList(),
events: events, events: events,
db: db, containers: cs,
} }
if err := m.loadExistingTasks(ctx); err != nil { if err := m.loadExistingTasks(ctx); err != nil {
return nil, err return nil, err
@ -105,7 +106,7 @@ type TaskManager struct {
tasks *runtime.TaskList tasks *runtime.TaskList
events *exchange.Exchange events *exchange.Exchange
db *metadata.DB containers containers.Store
} }
// ID of the task manager // ID of the task manager
@ -278,13 +279,8 @@ func (m *TaskManager) loadTasks(ctx context.Context) error {
} }
func (m *TaskManager) container(ctx context.Context, id string) (*containers.Container, error) { func (m *TaskManager) container(ctx context.Context, id string) (*containers.Container, error) {
var container containers.Container container, err := m.containers.Get(ctx, id)
if err := m.db.View(func(tx *bolt.Tx) error { if err != nil {
store := metadata.NewContainerStore(tx)
var err error
container, err = store.Get(ctx, id)
return err
}); err != nil {
return nil, err return nil, err
} }
return &container, nil return &container, nil

View File

@ -222,11 +222,14 @@ func (s *shim) Close() error {
} }
func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) { func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) {
response, err := s.task.Delete(ctx, &task.DeleteRequest{ response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{
ID: s.ID(), ID: s.ID(),
}) })
if err != nil && !errdefs.IsNotFound(err) { if shimErr != nil {
return nil, errdefs.FromGRPC(err) shimErr = errdefs.FromGRPC(shimErr)
if !errdefs.IsNotFound(shimErr) {
return nil, shimErr
}
} }
// remove self from the runtime task list // remove self from the runtime task list
// this seems dirty but it cleans up the API across runtimes, tasks, and the service // this seems dirty but it cleans up the API across runtimes, tasks, and the service
@ -238,6 +241,9 @@ func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) {
if err := s.bundle.Delete(); err != nil { if err := s.bundle.Delete(); err != nil {
log.G(ctx).WithError(err).Error("failed to delete bundle") log.G(ctx).WithError(err).Error("failed to delete bundle")
} }
if shimErr != nil {
return nil, shimErr
}
return &runtime.Exit{ return &runtime.Exit{
Status: response.ExitStatus, Status: response.ExitStatus,
Timestamp: response.ExitedAt, Timestamp: response.ExitedAt,

View File

@ -48,8 +48,11 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
db := m.(*metadata.DB)
return &local{ return &local{
db: m.(*metadata.DB), Store: metadata.NewContainerStore(db),
db: db,
publisher: ic.Events, publisher: ic.Events,
}, nil }, nil
}, },
@ -57,6 +60,7 @@ func init() {
} }
type local struct { type local struct {
containers.Store
db *metadata.DB db *metadata.DB
publisher events.Publisher publisher events.Publisher
} }
@ -66,8 +70,8 @@ var _ api.ContainersClient = &local{}
func (l *local) Get(ctx context.Context, req *api.GetContainerRequest, _ ...grpc.CallOption) (*api.GetContainerResponse, error) { func (l *local) Get(ctx context.Context, req *api.GetContainerRequest, _ ...grpc.CallOption) (*api.GetContainerResponse, error) {
var resp api.GetContainerResponse var resp api.GetContainerResponse
return &resp, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context, store containers.Store) error { return &resp, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error {
container, err := store.Get(ctx, req.ID) container, err := l.Store.Get(ctx, req.ID)
if err != nil { if err != nil {
return err return err
} }
@ -80,8 +84,8 @@ func (l *local) Get(ctx context.Context, req *api.GetContainerRequest, _ ...grpc
func (l *local) List(ctx context.Context, req *api.ListContainersRequest, _ ...grpc.CallOption) (*api.ListContainersResponse, error) { func (l *local) List(ctx context.Context, req *api.ListContainersRequest, _ ...grpc.CallOption) (*api.ListContainersResponse, error) {
var resp api.ListContainersResponse var resp api.ListContainersResponse
return &resp, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context, store containers.Store) error { return &resp, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error {
containers, err := store.List(ctx, req.Filters...) containers, err := l.Store.List(ctx, req.Filters...)
if err != nil { if err != nil {
return err return err
} }
@ -94,8 +98,8 @@ func (l *local) ListStream(ctx context.Context, req *api.ListContainersRequest,
stream := &localStream{ stream := &localStream{
ctx: ctx, ctx: ctx,
} }
return stream, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context, store containers.Store) error { return stream, errdefs.ToGRPC(l.withStoreView(ctx, func(ctx context.Context) error {
containers, err := store.List(ctx, req.Filters...) containers, err := l.Store.List(ctx, req.Filters...)
if err != nil { if err != nil {
return err return err
} }
@ -107,10 +111,10 @@ func (l *local) ListStream(ctx context.Context, req *api.ListContainersRequest,
func (l *local) Create(ctx context.Context, req *api.CreateContainerRequest, _ ...grpc.CallOption) (*api.CreateContainerResponse, error) { func (l *local) Create(ctx context.Context, req *api.CreateContainerRequest, _ ...grpc.CallOption) (*api.CreateContainerResponse, error) {
var resp api.CreateContainerResponse var resp api.CreateContainerResponse
if err := l.withStoreUpdate(ctx, func(ctx context.Context, store containers.Store) error { if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
container := containerFromProto(&req.Container) container := containerFromProto(&req.Container)
created, err := store.Create(ctx, container) created, err := l.Store.Create(ctx, container)
if err != nil { if err != nil {
return err return err
} }
@ -144,13 +148,13 @@ func (l *local) Update(ctx context.Context, req *api.UpdateContainerRequest, _ .
container = containerFromProto(&req.Container) container = containerFromProto(&req.Container)
) )
if err := l.withStoreUpdate(ctx, func(ctx context.Context, store containers.Store) error { if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
var fieldpaths []string var fieldpaths []string
if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 { if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
fieldpaths = append(fieldpaths, req.UpdateMask.Paths...) fieldpaths = append(fieldpaths, req.UpdateMask.Paths...)
} }
updated, err := store.Update(ctx, container, fieldpaths...) updated, err := l.Store.Update(ctx, container, fieldpaths...)
if err != nil { if err != nil {
return err return err
} }
@ -174,8 +178,8 @@ func (l *local) Update(ctx context.Context, req *api.UpdateContainerRequest, _ .
} }
func (l *local) Delete(ctx context.Context, req *api.DeleteContainerRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) { func (l *local) Delete(ctx context.Context, req *api.DeleteContainerRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
if err := l.withStoreUpdate(ctx, func(ctx context.Context, store containers.Store) error { if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
return store.Delete(ctx, req.ID) return l.Store.Delete(ctx, req.ID)
}); err != nil { }); err != nil {
return &ptypes.Empty{}, errdefs.ToGRPC(err) return &ptypes.Empty{}, errdefs.ToGRPC(err)
} }
@ -189,15 +193,17 @@ func (l *local) Delete(ctx context.Context, req *api.DeleteContainerRequest, _ .
return &ptypes.Empty{}, nil return &ptypes.Empty{}, nil
} }
func (l *local) withStore(ctx context.Context, fn func(ctx context.Context, store containers.Store) error) func(tx *bolt.Tx) error { func (l *local) withStore(ctx context.Context, fn func(ctx context.Context) error) func(tx *bolt.Tx) error {
return func(tx *bolt.Tx) error { return fn(ctx, metadata.NewContainerStore(tx)) } return func(tx *bolt.Tx) error {
return fn(metadata.WithTransactionContext(ctx, tx))
}
} }
func (l *local) withStoreView(ctx context.Context, fn func(ctx context.Context, store containers.Store) error) error { func (l *local) withStoreView(ctx context.Context, fn func(ctx context.Context) error) error {
return l.db.View(l.withStore(ctx, fn)) return l.db.View(l.withStore(ctx, fn))
} }
func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context, store containers.Store) error) error { func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context) error) error {
return l.db.Update(l.withStore(ctx, fn)) return l.db.Update(l.withStore(ctx, fn))
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/containerd/containerd/metadata" "github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services" "github.com/containerd/containerd/services"
bolt "go.etcd.io/bbolt"
) )
func init() { func init() {
@ -44,7 +43,7 @@ func init() {
return nil, err return nil, err
} }
return &local{ return &local{
db: m.(*metadata.DB), Manager: metadata.NewLeaseManager(m.(*metadata.DB)),
gc: g.(gcScheduler), gc: g.(gcScheduler),
}, nil }, nil
}, },
@ -56,22 +55,10 @@ type gcScheduler interface {
} }
type local struct { type local struct {
db *metadata.DB leases.Manager
gc gcScheduler gc gcScheduler
} }
func (l *local) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) {
var lease leases.Lease
if err := l.db.Update(func(tx *bolt.Tx) error {
var err error
lease, err = metadata.NewLeaseManager(tx).Create(ctx, opts...)
return err
}); err != nil {
return leases.Lease{}, err
}
return lease, nil
}
func (l *local) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error { func (l *local) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error {
var do leases.DeleteOptions var do leases.DeleteOptions
for _, opt := range opts { for _, opt := range opts {
@ -80,9 +67,7 @@ func (l *local) Delete(ctx context.Context, lease leases.Lease, opts ...leases.D
} }
} }
if err := l.db.Update(func(tx *bolt.Tx) error { if err := l.Manager.Delete(ctx, lease); err != nil {
return metadata.NewLeaseManager(tx).Delete(ctx, lease)
}); err != nil {
return err return err
} }
@ -95,39 +80,3 @@ func (l *local) Delete(ctx context.Context, lease leases.Lease, opts ...leases.D
return nil return nil
} }
func (l *local) List(ctx context.Context, filters ...string) ([]leases.Lease, error) {
var ll []leases.Lease
if err := l.db.View(func(tx *bolt.Tx) error {
var err error
ll, err = metadata.NewLeaseManager(tx).List(ctx, filters...)
return err
}); err != nil {
return nil, err
}
return ll, nil
}
func (l *local) AddResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
return l.db.Update(func(tx *bolt.Tx) error {
return metadata.NewLeaseManager(tx).AddResource(ctx, lease, r)
})
}
func (l *local) DeleteResource(ctx context.Context, lease leases.Lease, r leases.Resource) error {
return l.db.Update(func(tx *bolt.Tx) error {
return metadata.NewLeaseManager(tx).DeleteResource(ctx, lease, r)
})
}
func (l *local) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) {
var rs []leases.Resource
if err := l.db.View(func(tx *bolt.Tx) error {
var err error
rs, err = metadata.NewLeaseManager(tx).ListResources(ctx, lease)
return err
}); err != nil {
return nil, err
}
return rs, nil
}

View File

@ -51,7 +51,6 @@ import (
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@ -101,11 +100,11 @@ func initFunc(ic *plugin.InitContext) (interface{}, error) {
monitor = runtime.NewNoopMonitor() monitor = runtime.NewNoopMonitor()
} }
cs := m.(*metadata.DB).ContentStore() db := m.(*metadata.DB)
l := &local{ l := &local{
runtimes: runtimes, runtimes: runtimes,
db: m.(*metadata.DB), containers: metadata.NewContainerStore(db),
store: cs, store: db.ContentStore(),
publisher: ic.Events, publisher: ic.Events,
monitor: monitor.(runtime.TaskMonitor), monitor: monitor.(runtime.TaskMonitor),
v2Runtime: v2r.(*v2.TaskManager), v2Runtime: v2r.(*v2.TaskManager),
@ -124,7 +123,7 @@ func initFunc(ic *plugin.InitContext) (interface{}, error) {
type local struct { type local struct {
runtimes map[string]runtime.PlatformRuntime runtimes map[string]runtime.PlatformRuntime
db *metadata.DB containers containers.Store
store content.Store store content.Store
publisher events.Publisher publisher events.Publisher
@ -242,7 +241,7 @@ func (l *local) Delete(ctx context.Context, r *api.DeleteTaskRequest, _ ...grpc.
} }
exit, err := t.Delete(ctx) exit, err := t.Delete(ctx)
if err != nil { if err != nil {
return nil, err return nil, errdefs.ToGRPC(err)
} }
return &api.DeleteResponse{ return &api.DeleteResponse{
ExitStatus: exit.Status, ExitStatus: exit.Status,
@ -258,7 +257,7 @@ func (l *local) DeleteProcess(ctx context.Context, r *api.DeleteProcessRequest,
} }
process, err := t.Process(ctx, r.ExecID) process, err := t.Process(ctx, r.ExecID)
if err != nil { if err != nil {
return nil, err return nil, errdefs.ToGRPC(err)
} }
exit, err := process.Delete(ctx) exit, err := process.Delete(ctx)
if err != nil { if err != nil {
@ -647,12 +646,8 @@ func (l *local) writeContent(ctx context.Context, mediaType, ref string, r io.Re
func (l *local) getContainer(ctx context.Context, id string) (*containers.Container, error) { func (l *local) getContainer(ctx context.Context, id string) (*containers.Container, error) {
var container containers.Container var container containers.Container
if err := l.db.View(func(tx *bolt.Tx) error { container, err := l.containers.Get(ctx, id)
store := metadata.NewContainerStore(tx) if err != nil {
var err error
container, err = store.Get(ctx, id)
return err
}); err != nil {
return nil, errdefs.ToGRPC(err) return nil, errdefs.ToGRPC(err)
} }
return &container, nil return &container, nil

View File

@ -1,6 +1,6 @@
github.com/containerd/go-runc e029b79d8cda8374981c64eba71f28ec38e5526f github.com/containerd/go-runc e029b79d8cda8374981c64eba71f28ec38e5526f
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9 github.com/containerd/cgroups abd0b19954a6b05e0963f48427062d1481b7faad
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13 github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877 github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877
@ -20,7 +20,7 @@ github.com/gogo/protobuf v1.2.1
github.com/gogo/googleapis v1.2.0 github.com/gogo/googleapis v1.2.0
github.com/golang/protobuf v1.2.0 github.com/golang/protobuf v1.2.0
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
github.com/opencontainers/runc f4982d86f7fde0b6f953cc62ccc4022c519a10a9 # v1.0.0-rc8-32-gf4982d86 github.com/opencontainers/runc d736ef14f0288d6993a1845745d6756cfc9ddd5a # v1.0.0-rc9
github.com/konsorten/go-windows-terminal-sequences v1.0.1 github.com/konsorten/go-windows-terminal-sequences v1.0.1
github.com/sirupsen/logrus v1.4.1 github.com/sirupsen/logrus v1.4.1
github.com/urfave/cli v1.22.0 github.com/urfave/cli v1.22.0
@ -51,34 +51,34 @@ github.com/cpuguy83/go-md2man v1.0.10
github.com/russross/blackfriday v1.5.2 github.com/russross/blackfriday v1.5.2
# cri dependencies # cri dependencies
github.com/containerd/cri f4d75d321c89b8d89bae570a7d2da1b3846c096f # release/1.3 github.com/containerd/cri 0ebf032aac5f6029f95a94e42161e9db7a7e84df # release/1.3+
github.com/containerd/go-cni 49fbd9b210f3c8ee3b7fd3cd797aabaf364627c1 github.com/containerd/go-cni 0d360c50b10b350b6bb23863fd4dfb1c232b01c9
github.com/containernetworking/cni v0.7.1 github.com/containernetworking/cni v0.7.1
github.com/containernetworking/plugins v0.7.6 github.com/containernetworking/plugins v0.7.6
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580 github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528 github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
github.com/emicklei/go-restful v2.2.1 github.com/emicklei/go-restful v2.9.5
github.com/google/gofuzz 24818f796faf91cd76ec7bddd72458fbced7a6c1 github.com/google/gofuzz v1.0.0
github.com/json-iterator/go 1.1.5 github.com/json-iterator/go v1.1.7
github.com/modern-go/reflect2 1.0.1 github.com/modern-go/reflect2 1.0.1
github.com/modern-go/concurrent 1.0.3 github.com/modern-go/concurrent 1.0.3
github.com/opencontainers/selinux v1.2.2 github.com/opencontainers/selinux v1.2.2
github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang v0.9.1
github.com/tchap/go-patricia v2.2.6 github.com/tchap/go-patricia v2.2.6
golang.org/x/crypto 88737f569e3a9c7ab309cdc09a07fe7fc87233c3 golang.org/x/crypto 5c40567a22f818bd14a1ea7245dad9f8ef0691aa
golang.org/x/oauth2 9f3314589c9a9136388751d9adae6b0ed400978a golang.org/x/oauth2 0f29369cfe4552d0e4bcddc57cc75f4d7e672a33
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631 golang.org/x/time 85acf8d2951cb2a3bde7632f9ff273ef0379bcbd
gopkg.in/inf.v0 v0.9.0 gopkg.in/inf.v0 v0.9.0
gopkg.in/yaml.v2 v2.2.1 gopkg.in/yaml.v2 v2.2.2
k8s.io/api kubernetes-1.15.0 k8s.io/api kubernetes-1.16.0-rc.2
k8s.io/apimachinery kubernetes-1.15.0 k8s.io/apimachinery kubernetes-1.16.0-rc.2
k8s.io/apiserver kubernetes-1.15.0 k8s.io/apiserver kubernetes-1.16.0-rc.2
k8s.io/cri-api kubernetes-1.15.0 k8s.io/cri-api kubernetes-1.16.0-rc.2
k8s.io/client-go kubernetes-1.15.0 k8s.io/client-go kubernetes-1.16.0-rc.2
k8s.io/klog v0.3.1 k8s.io/klog v0.4.0
k8s.io/kubernetes v1.15.0 k8s.io/kubernetes v1.16.0-rc.2
k8s.io/utils c2654d5206da6b7b6ace12841e8f359bb89b443c k8s.io/utils c2654d5206da6b7b6ace12841e8f359bb89b443c
sigs.k8s.io/yaml v1.1.0 sigs.k8s.io/yaml v1.1.0

View File

@ -16,14 +16,19 @@
package version package version
import "runtime"
var ( var (
// Package is filled at linking time // Package is filled at linking time
Package = "github.com/containerd/containerd" Package = "github.com/containerd/containerd"
// Version holds the complete version number. Filled in at linking time. // Version holds the complete version number. Filled in at linking time.
Version = "1.2.0+unknown" Version = "1.3.0+unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build // Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time. // the program at linking time.
Revision = "" Revision = ""
// GoVersion is Go tree's version.
GoVersion = runtime.Version()
) )

View File

@ -1,40 +0,0 @@
package errcode
import (
"encoding/json"
"net/http"
)
// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err
// and sets the content-type header to 'application/json'. It will handle
// ErrorCoder and Errors, and if necessary will create an envelope.
func ServeJSON(w http.ResponseWriter, err error) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
var sc int
switch errs := err.(type) {
case Errors:
if len(errs) < 1 {
break
}
if err, ok := errs[0].(ErrorCoder); ok {
sc = err.ErrorCode().Descriptor().HTTPStatusCode
}
case ErrorCoder:
sc = errs.ErrorCode().Descriptor().HTTPStatusCode
err = Errors{err} // create an envelope.
default:
// We just have an unhandled error type, so just place in an envelope
// and move along.
err = Errors{err}
}
if sc == 0 {
sc = http.StatusInternalServerError
}
w.WriteHeader(sc)
return json.NewEncoder(w).Encode(err)
}

View File

@ -261,6 +261,7 @@ process := &libcontainer.Process{
Stdin: os.Stdin, Stdin: os.Stdin,
Stdout: os.Stdout, Stdout: os.Stdout,
Stderr: os.Stderr, Stderr: os.Stderr,
Init: true,
} }
err := container.Run(process) err := container.Run(process)

View File

@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/opencontainers/runc/libcontainer/utils"
) )
// IsEnabled returns true if apparmor is enabled for the host. // IsEnabled returns true if apparmor is enabled for the host.
@ -19,7 +21,7 @@ func IsEnabled() bool {
return false return false
} }
func setprocattr(attr, value string) error { func setProcAttr(attr, value string) error {
// Under AppArmor you can only change your own attr, so use /proc/self/ // Under AppArmor you can only change your own attr, so use /proc/self/
// instead of /proc/<tid>/ like libapparmor does // instead of /proc/<tid>/ like libapparmor does
path := fmt.Sprintf("/proc/self/attr/%s", attr) path := fmt.Sprintf("/proc/self/attr/%s", attr)
@ -30,6 +32,10 @@ func setprocattr(attr, value string) error {
} }
defer f.Close() defer f.Close()
if err := utils.EnsureProcHandle(f); err != nil {
return err
}
_, err = fmt.Fprintf(f, "%s", value) _, err = fmt.Fprintf(f, "%s", value)
return err return err
} }
@ -37,7 +43,7 @@ func setprocattr(attr, value string) error {
// changeOnExec reimplements aa_change_onexec from libapparmor in Go // changeOnExec reimplements aa_change_onexec from libapparmor in Go
func changeOnExec(name string) error { func changeOnExec(name string) error {
value := "exec " + name value := "exec " + name
if err := setprocattr("exec", value); err != nil { if err := setProcAttr("exec", value); err != nil {
return fmt.Errorf("apparmor failed to apply profile: %s", err) return fmt.Errorf("apparmor failed to apply profile: %s", err)
} }
return nil return nil

View File

@ -59,3 +59,8 @@ func NewThrottleDevice(major, minor int64, rate uint64) *ThrottleDevice {
func (td *ThrottleDevice) String() string { func (td *ThrottleDevice) String() string {
return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate) return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate)
} }
// StringName formats the struct to be writable to the cgroup specific file
func (td *ThrottleDevice) StringName(name string) string {
return fmt.Sprintf("%d:%d %s=%d", td.Major, td.Minor, name, td.Rate)
}

View File

@ -119,4 +119,12 @@ type Resources struct {
// Set class identifier for container's network packets // Set class identifier for container's network packets
NetClsClassid uint32 `json:"net_cls_classid_u"` NetClsClassid uint32 `json:"net_cls_classid_u"`
// Used on cgroups v2:
// CpuWeight sets a proportional bandwidth limit.
CpuWeight uint64 `json:"cpu_weight"`
// CpuMax sets she maximum bandwidth limit (format: max period).
CpuMax string `json:"cpu_max"`
} }

View File

@ -1,3 +1,5 @@
// +build !linux
package configs package configs
// TODO Windows: This can ultimately be entirely factored out on Windows as // TODO Windows: This can ultimately be entirely factored out on Windows as

View File

@ -44,6 +44,7 @@ const (
Trap Trap
Allow Allow
Trace Trace
Log
) )
// Operator is a comparison operator to be used when matching syscall arguments in Seccomp // Operator is a comparison operator to be used when matching syscall arguments in Seccomp

View File

@ -7,11 +7,11 @@ import (
"path/filepath" "path/filepath"
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
var ( var (
// ErrNotADevice denotes that a file is not a valid linux device.
ErrNotADevice = errors.New("not a device node") ErrNotADevice = errors.New("not a device node")
) )
@ -21,7 +21,8 @@ var (
ioutilReadDir = ioutil.ReadDir ioutilReadDir = ioutil.ReadDir
) )
// Given the path to a device and its cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct. // Given the path to a device and its cgroup_permissions(which cannot be easily queried) look up the
// information about a linux device and return that information as a Device struct.
func DeviceFromPath(path, permissions string) (*configs.Device, error) { func DeviceFromPath(path, permissions string) (*configs.Device, error) {
var stat unix.Stat_t var stat unix.Stat_t
err := unixLstat(path, &stat) err := unixLstat(path, &stat)
@ -60,25 +61,29 @@ func DeviceFromPath(path, permissions string) (*configs.Device, error) {
}, nil }, nil
} }
// HostDevices returns all devices that can be found under /dev directory.
func HostDevices() ([]*configs.Device, error) { func HostDevices() ([]*configs.Device, error) {
return getDevices("/dev") return GetDevices("/dev")
} }
func getDevices(path string) ([]*configs.Device, error) { // GetDevices recursively traverses a directory specified by path
// and returns all devices found there.
func GetDevices(path string) ([]*configs.Device, error) {
files, err := ioutilReadDir(path) files, err := ioutilReadDir(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
out := []*configs.Device{} var out []*configs.Device
for _, f := range files { for _, f := range files {
switch { switch {
case f.IsDir(): case f.IsDir():
switch f.Name() { switch f.Name() {
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": // ".udev" added to address https://github.com/opencontainers/runc/issues/2093
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
continue continue
default: default:
sub, err := getDevices(filepath.Join(path, f.Name())) sub, err := GetDevices(filepath.Join(path, f.Name()))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,5 +1,3 @@
// +build !linux !cgo // +build !linux !cgo
package nsenter package nsenter
import "C"

View File

@ -50,9 +50,6 @@ enum sync_t {
#define JUMP_CHILD 0xA0 #define JUMP_CHILD 0xA0
#define JUMP_INIT 0xA1 #define JUMP_INIT 0xA1
/* JSON buffer. */
#define JSON_MAX 4096
/* Assume the stack grows down, so arguments should be above it. */ /* Assume the stack grows down, so arguments should be above it. */
struct clone_t { struct clone_t {
/* /*
@ -148,11 +145,11 @@ static void write_log_with_info(const char *level, const char *function, int lin
va_start(args, format); va_start(args, format);
if (vsnprintf(message, sizeof(message), format, args) < 0) if (vsnprintf(message, sizeof(message), format, args) < 0)
return; goto done;
va_end(args);
if (dprintf(logfd, "{\"level\":\"%s\", \"msg\": \"%s:%d %s\"}\n", level, function, line, message) < 0) dprintf(logfd, "{\"level\":\"%s\", \"msg\": \"%s:%d %s\"}\n", level, function, line, message);
return; done:
va_end(args);
} }
#define write_log(level, fmt, ...) \ #define write_log(level, fmt, ...) \

View File

@ -22,6 +22,7 @@ var actions = map[string]configs.Action{
"SCMP_ACT_TRAP": configs.Trap, "SCMP_ACT_TRAP": configs.Trap,
"SCMP_ACT_ALLOW": configs.Allow, "SCMP_ACT_ALLOW": configs.Allow,
"SCMP_ACT_TRACE": configs.Trace, "SCMP_ACT_TRACE": configs.Trace,
"SCMP_ACT_LOG": configs.Log,
} }
var archs = map[string]string{ var archs = map[string]string{

View File

@ -19,6 +19,7 @@ var (
actTrap = libseccomp.ActTrap actTrap = libseccomp.ActTrap
actKill = libseccomp.ActKill actKill = libseccomp.ActKill
actTrace = libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM)) actTrace = libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM))
actLog = libseccomp.ActLog
actErrno = libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM)) actErrno = libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM))
) )
@ -112,6 +113,8 @@ func getAction(act configs.Action) (libseccomp.ScmpAction, error) {
return actAllow, nil return actAllow, nil
case configs.Trace: case configs.Trace:
return actTrace, nil return actTrace, nil
case configs.Log:
return actLog, nil
default: default:
return libseccomp.ActInvalid, fmt.Errorf("invalid action, cannot use in rule") return libseccomp.ActInvalid, fmt.Errorf("invalid action, cannot use in rule")
} }

View File

@ -1,5 +1,5 @@
// +build linux // +build linux
// +build arm64 amd64 mips mipsle mips64 mips64le ppc ppc64 ppc64le s390x // +build arm64 amd64 mips mipsle mips64 mips64le ppc ppc64 ppc64le riscv64 s390x
package system package system

View File

@ -0,0 +1,93 @@
// +build linux
package utils
/*
* Copyright 2016, 2017 SUSE LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
// MaxSendfdLen is the maximum length of the name of a file descriptor being
// sent using SendFd. The name of the file handle returned by RecvFd will never
// be larger than this value.
const MaxNameLen = 4096
// oobSpace is the size of the oob slice required to store a single FD. Note
// that unix.UnixRights appears to make the assumption that fd is always int32,
// so sizeof(fd) = 4.
var oobSpace = unix.CmsgSpace(4)
// RecvFd waits for a file descriptor to be sent over the given AF_UNIX
// socket. The file name of the remote file descriptor will be recreated
// locally (it is sent as non-auxiliary data in the same payload).
func RecvFd(socket *os.File) (*os.File, error) {
// For some reason, unix.Recvmsg uses the length rather than the capacity
// when passing the msg_controllen and other attributes to recvmsg. So we
// have to actually set the length.
name := make([]byte, MaxNameLen)
oob := make([]byte, oobSpace)
sockfd := socket.Fd()
n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0)
if err != nil {
return nil, err
}
if n >= MaxNameLen || oobn != oobSpace {
return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
}
// Truncate.
name = name[:n]
oob = oob[:oobn]
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return nil, err
}
if len(scms) != 1 {
return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
}
scm := scms[0]
fds, err := unix.ParseUnixRights(&scm)
if err != nil {
return nil, err
}
if len(fds) != 1 {
return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
}
fd := uintptr(fds[0])
return os.NewFile(fd, string(name)), nil
}
// SendFd sends a file descriptor over the given AF_UNIX socket. In
// addition, the file.Name() of the given file will also be sent as
// non-auxiliary data in the same payload (allowing to send contextual
// information for a file descriptor).
func SendFd(socket *os.File, name string, fd uintptr) error {
if len(name) >= MaxNameLen {
return fmt.Errorf("sendfd: filename too long: %s", name)
}
oob := unix.UnixRights(int(fd))
return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0)
}

View File

@ -0,0 +1,112 @@
package utils
import (
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
"unsafe"
"golang.org/x/sys/unix"
)
const (
exitSignalOffset = 128
)
// ResolveRootfs ensures that the current working directory is
// not a symlink and returns the absolute path to the rootfs
func ResolveRootfs(uncleanRootfs string) (string, error) {
rootfs, err := filepath.Abs(uncleanRootfs)
if err != nil {
return "", err
}
return filepath.EvalSymlinks(rootfs)
}
// ExitStatus returns the correct exit status for a process based on if it
// was signaled or exited cleanly
func ExitStatus(status unix.WaitStatus) int {
if status.Signaled() {
return exitSignalOffset + int(status.Signal())
}
return status.ExitStatus()
}
// WriteJSON writes the provided struct v to w using standard json marshaling
func WriteJSON(w io.Writer, v interface{}) error {
data, err := json.Marshal(v)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
// CleanPath makes a path safe for use with filepath.Join. This is done by not
// only cleaning the path, but also (if the path is relative) adding a leading
// '/' and cleaning it (then removing the leading '/'). This ensures that a
// path resulting from prepending another path will always resolve to lexically
// be a subdirectory of the prefixed path. This is all done lexically, so paths
// that include symlinks won't be safe as a result of using CleanPath.
func CleanPath(path string) string {
// Deal with empty strings nicely.
if path == "" {
return ""
}
// Ensure that all paths are cleaned (especially problematic ones like
// "/../../../../../" which can cause lots of issues).
path = filepath.Clean(path)
// If the path isn't absolute, we need to do more processing to fix paths
// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
// paths to relative ones.
if !filepath.IsAbs(path) {
path = filepath.Clean(string(os.PathSeparator) + path)
// This can't fail, as (by definition) all paths are relative to root.
path, _ = filepath.Rel(string(os.PathSeparator), path)
}
// Clean the path again for good measure.
return filepath.Clean(path)
}
// SearchLabels searches a list of key-value pairs for the provided key and
// returns the corresponding value. The pairs must be separated with '='.
func SearchLabels(labels []string, query string) string {
for _, l := range labels {
parts := strings.SplitN(l, "=", 2)
if len(parts) < 2 {
continue
}
if parts[0] == query {
return parts[1]
}
}
return ""
}
// Annotations returns the bundle path and user defined annotations from the
// libcontainer state. We need to remove the bundle because that is a label
// added by libcontainer.
func Annotations(labels []string) (bundle string, userAnnotations map[string]string) {
userAnnotations = make(map[string]string)
for _, l := range labels {
parts := strings.SplitN(l, "=", 2)
if len(parts) < 2 {
continue
}
if parts[0] == "bundle" {
bundle = parts[1]
} else {
userAnnotations[parts[0]] = parts[1]
}
}
return
}
func GetIntSize() int {
return int(unsafe.Sizeof(1))
}

View File

@ -0,0 +1,68 @@
// +build !windows
package utils
import (
"fmt"
"os"
"strconv"
"golang.org/x/sys/unix"
)
// EnsureProcHandle returns whether or not the given file handle is on procfs.
func EnsureProcHandle(fh *os.File) error {
var buf unix.Statfs_t
if err := unix.Fstatfs(int(fh.Fd()), &buf); err != nil {
return fmt.Errorf("ensure %s is on procfs: %v", fh.Name(), err)
}
if buf.Type != unix.PROC_SUPER_MAGIC {
return fmt.Errorf("%s is not on procfs", fh.Name())
}
return nil
}
// CloseExecFrom applies O_CLOEXEC to all file descriptors currently open for
// the process (except for those below the given fd value).
func CloseExecFrom(minFd int) error {
fdDir, err := os.Open("/proc/self/fd")
if err != nil {
return err
}
defer fdDir.Close()
if err := EnsureProcHandle(fdDir); err != nil {
return err
}
fdList, err := fdDir.Readdirnames(-1)
if err != nil {
return err
}
for _, fdStr := range fdList {
fd, err := strconv.Atoi(fdStr)
// Ignore non-numeric file names.
if err != nil {
continue
}
// Ignore descriptors lower than our specified minimum.
if fd < minFd {
continue
}
// Intentionally ignore errors from unix.CloseOnExec -- the cases where
// this might fail are basically file descriptors that have already
// been closed (including and especially the one that was created when
// ioutil.ReadDir did the "opendir" syscall).
unix.CloseOnExec(fd)
}
return nil
}
// NewSockPair returns a new unix socket pair
func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
if err != nil {
return nil, nil, err
}
return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
}

View File

@ -1,26 +1,28 @@
# OCI runtime-spec. When updating this, make sure you use a version tag rather # OCI runtime-spec. When updating this, make sure you use a version tag rather
# than a commit ID so it's much more obvious what version of the spec we are # than a commit ID so it's much more obvious what version of the spec we are
# using. # using.
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
# Core libcontainer functionality. # Core libcontainer functionality.
github.com/checkpoint-restore/go-criu v3.11 github.com/checkpoint-restore/go-criu 17b0214f6c48980c45dc47ecb0cfd6d9e02df723 # v3.11
github.com/mrunalp/fileutils ed869b029674c0e9ce4c0dfa781405c2d9946d08 github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7
github.com/opencontainers/selinux v1.2.2 github.com/opencontainers/selinux 5215b1806f52b1fcc2070a8826c542c9d33cd3cf # v1.3.0 (+ CVE-2019-16884)
github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang 689e3c1541a84461afc49c1c87352a6cedf72e9c # v0.9.1
github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16 github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
github.com/vishvananda/netlink 1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270 github.com/vishvananda/netlink 1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270
# systemd integration. # systemd integration.
github.com/coreos/go-systemd v14 github.com/coreos/go-systemd 95778dfbb74eb7e4dbaf43bf7d71809650ef8076 # v19
github.com/coreos/pkg v3 github.com/godbus/dbus 2ff6f7ffd60f0f2410b3105864bdd12c7894f844 # v5.0.1
github.com/godbus/dbus v3 github.com/golang/protobuf 925541529c1fa6821df4e44ce2723319eb2be768 # v1.0.0
github.com/golang/protobuf 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8
# Command-line interface. # Command-line interface.
github.com/cyphar/filepath-securejoin v0.2.1 github.com/cyphar/filepath-securejoin a261ee33d7a517f054effbf451841abaafe3e0fd # v0.2.2
github.com/docker/go-units v0.2.0 github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3
github.com/urfave/cli d53eb991652b1d438abdd34ce4bfa3ef1539108e github.com/urfave/cli cfb38830724cc34fedffe9a2a29fb54fa9169cd1 # v1.20.0
golang.org/x/sys 41f3e6584952bb034a481797859f6ab34b6803bd https://github.com/golang/sys golang.org/x/sys 9eafafc0a87e0fd0aeeba439a4573537970c44c7 https://github.com/golang/sys
# console dependencies # console dependencies
github.com/containerd/console 2748ece16665b45a47f884001d5831ec79703880 github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/pkg/errors v0.8.0 github.com/pkg/errors ba968bfe8b2f7e042a574c888954fccecfa385b4 # v0.8.1