
Signed-off-by: Derek McGowan <derek@mcg.dev> Update progress to reference parents Signed-off-by: Derek McGowan <derek@mcg.dev> Update Progress logic Signed-off-by: Derek McGowan <derek@mcg.dev>
222 lines
5.2 KiB
Go
222 lines
5.2 KiB
Go
/*
|
|
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 local
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/log"
|
|
"github.com/containerd/containerd/pkg/transfer"
|
|
"github.com/containerd/containerd/remotes"
|
|
"github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
type ProgressTracker struct {
|
|
root string
|
|
cs content.Store
|
|
added chan jobUpdate
|
|
waitC chan struct{}
|
|
|
|
parents map[digest.Digest][]ocispec.Descriptor
|
|
parentL sync.Mutex
|
|
}
|
|
|
|
type jobState uint8
|
|
|
|
const (
|
|
jobAdded jobState = iota
|
|
jobInProgress
|
|
jobComplete
|
|
)
|
|
|
|
type jobStatus struct {
|
|
state jobState
|
|
name string
|
|
parents []string
|
|
progress int64
|
|
desc ocispec.Descriptor
|
|
}
|
|
|
|
type jobUpdate struct {
|
|
desc ocispec.Descriptor
|
|
exists bool
|
|
//children []ocispec.Descriptor
|
|
}
|
|
|
|
// NewProgressTracker tracks content download progress
|
|
func NewProgressTracker(root string, cs content.Store) *ProgressTracker {
|
|
return &ProgressTracker{
|
|
root: root,
|
|
cs: cs,
|
|
added: make(chan jobUpdate, 1),
|
|
waitC: make(chan struct{}),
|
|
parents: map[digest.Digest][]ocispec.Descriptor{},
|
|
}
|
|
}
|
|
|
|
func (j *ProgressTracker) HandleProgress(ctx context.Context, pf transfer.ProgressFunc) {
|
|
defer close(j.waitC)
|
|
// Instead of ticker, just delay
|
|
jobs := map[digest.Digest]*jobStatus{}
|
|
tc := time.NewTicker(time.Millisecond * 200)
|
|
for {
|
|
select {
|
|
case update := <-j.added:
|
|
job, ok := jobs[update.desc.Digest]
|
|
if !ok {
|
|
|
|
// Only captures the parents defined before,
|
|
// could handle parent updates in same thread
|
|
// if there is a synchronization issue
|
|
var parents []string
|
|
j.parentL.Lock()
|
|
for _, parent := range j.parents[update.desc.Digest] {
|
|
parents = append(parents, remotes.MakeRefKey(ctx, parent))
|
|
}
|
|
j.parentL.Unlock()
|
|
if len(parents) == 0 {
|
|
parents = []string{j.root}
|
|
}
|
|
name := remotes.MakeRefKey(ctx, update.desc)
|
|
|
|
job = &jobStatus{
|
|
state: jobAdded,
|
|
name: name,
|
|
parents: parents,
|
|
desc: update.desc,
|
|
}
|
|
jobs[update.desc.Digest] = job
|
|
pf(transfer.Progress{
|
|
Event: "waiting",
|
|
Name: name,
|
|
Parents: parents,
|
|
//Digest: desc.Digest.String(),
|
|
Progress: 0,
|
|
Total: update.desc.Size,
|
|
})
|
|
}
|
|
if update.exists {
|
|
pf(transfer.Progress{
|
|
Event: "already exists",
|
|
Name: remotes.MakeRefKey(ctx, update.desc),
|
|
Progress: update.desc.Size,
|
|
Total: update.desc.Size,
|
|
})
|
|
job.state = jobComplete
|
|
job.progress = job.desc.Size
|
|
}
|
|
|
|
case <-tc.C:
|
|
// TODO: Filter by references
|
|
active, err := j.cs.ListStatuses(ctx)
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Error("failed to list statuses for progress")
|
|
}
|
|
sort.Slice(active, func(i, j int) bool {
|
|
return active[i].Ref < active[j].Ref
|
|
})
|
|
|
|
for dgst, job := range jobs {
|
|
if job.state != jobComplete {
|
|
idx := sort.Search(len(active), func(i int) bool { return active[i].Ref >= job.name })
|
|
if idx < len(active) && active[idx].Ref == job.name {
|
|
if active[idx].Offset > job.progress {
|
|
pf(transfer.Progress{
|
|
Event: "downloading",
|
|
Name: job.name,
|
|
Parents: job.parents,
|
|
//Digest: job.desc.Digest.String(),
|
|
Progress: active[idx].Offset,
|
|
Total: active[idx].Total,
|
|
})
|
|
job.progress = active[idx].Offset
|
|
job.state = jobInProgress
|
|
jobs[dgst] = job
|
|
}
|
|
} else {
|
|
_, err := j.cs.Info(ctx, job.desc.Digest)
|
|
if err == nil {
|
|
pf(transfer.Progress{
|
|
Event: "complete",
|
|
Name: job.name,
|
|
Parents: job.parents,
|
|
//Digest: job.desc.Digest.String(),
|
|
Progress: job.desc.Size,
|
|
Total: job.desc.Size,
|
|
})
|
|
|
|
}
|
|
job.state = jobComplete
|
|
jobs[dgst] = job
|
|
}
|
|
}
|
|
}
|
|
// Next timer?
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add adds a descriptor to be tracked
|
|
func (j *ProgressTracker) Add(desc ocispec.Descriptor) {
|
|
if j == nil {
|
|
return
|
|
}
|
|
j.added <- jobUpdate{
|
|
desc: desc,
|
|
}
|
|
}
|
|
|
|
func (j *ProgressTracker) MarkExists(desc ocispec.Descriptor) {
|
|
if j == nil {
|
|
return
|
|
}
|
|
j.added <- jobUpdate{
|
|
desc: desc,
|
|
exists: true,
|
|
}
|
|
|
|
}
|
|
|
|
// Adds hierarchy information
|
|
func (j *ProgressTracker) AddChildren(desc ocispec.Descriptor, children []ocispec.Descriptor) {
|
|
if j == nil || len(children) == 0 {
|
|
return
|
|
}
|
|
j.parentL.Lock()
|
|
defer j.parentL.Unlock()
|
|
for _, child := range children {
|
|
j.parents[child.Digest] = append(j.parents[child.Digest], desc)
|
|
}
|
|
|
|
}
|
|
|
|
func (j *ProgressTracker) Wait() {
|
|
// timeout rather than rely on cancel
|
|
timeout := time.After(10 * time.Second)
|
|
select {
|
|
case <-timeout:
|
|
case <-j.waitC:
|
|
}
|
|
}
|