Merge pull request #235 from yanxuean/truncindex
Add Truncindex for container, sandbox and image
This commit is contained in:
commit
61d598d00f
@ -52,7 +52,7 @@ func (c *criContainerdService) attachContainer(ctx context.Context, id string, s
|
||||
// Get container from our container store.
|
||||
cntr, err := c.containerStore.Get(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find container in store: %v", err)
|
||||
return fmt.Errorf("failed to find container %q in store: %v", id, err)
|
||||
}
|
||||
id = cntr.ID
|
||||
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
func (c *criContainerdService) Exec(ctx context.Context, r *runtime.ExecRequest) (*runtime.ExecResponse, error) {
|
||||
cntr, err := c.containerStore.Get(r.GetContainerId())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find container in store: %v", err)
|
||||
return nil, fmt.Errorf("failed to find container %q in store: %v", r.GetContainerId(), err)
|
||||
}
|
||||
state := cntr.Status.Get().State()
|
||||
if state != runtime.ContainerState_CONTAINER_RUNNING {
|
||||
|
@ -78,7 +78,7 @@ func (c *criContainerdService) execInContainer(ctx context.Context, id string, o
|
||||
// Get container from our container store.
|
||||
cntr, err := c.containerStore.Get(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find container in store: %v", err)
|
||||
return nil, fmt.Errorf("failed to find container %q in store: %v", id, err)
|
||||
}
|
||||
id = cntr.ID
|
||||
|
||||
|
@ -142,7 +142,9 @@ func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullIma
|
||||
img.RepoTags = []string{repoTag}
|
||||
}
|
||||
|
||||
c.imageStore.Add(img)
|
||||
if err := c.imageStore.Add(img); err != nil {
|
||||
return nil, fmt.Errorf("failed to add image %q into store: %v", img.ID, err)
|
||||
}
|
||||
|
||||
// NOTE(random-liu): the actual state in containerd is the source of truth, even we maintain
|
||||
// in-memory image store, it's only for in-memory indexing. The image could be removed
|
||||
|
@ -101,8 +101,10 @@ func (c *criContainerdService) recover(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to load images: %v", err)
|
||||
}
|
||||
for _, image := range images {
|
||||
c.imageStore.Add(image)
|
||||
glog.V(4).Infof("Loaded image %+v", image)
|
||||
if err := c.imageStore.Add(image); err != nil {
|
||||
return fmt.Errorf("failed to add image %q to store: %v", image.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// It's possible that containerd containers are deleted unexpectedly. In that case,
|
||||
|
@ -35,7 +35,7 @@ func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortF
|
||||
// TODO(random-liu): Run a socat container inside the sandbox to do portforward.
|
||||
sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find sandbox: %v", err)
|
||||
return nil, fmt.Errorf("failed to find sandbox %q: %v", r.GetPodSandboxId(), err)
|
||||
}
|
||||
|
||||
t, err := sandbox.Container.Task(ctx, nil)
|
||||
@ -59,7 +59,7 @@ func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortF
|
||||
func (c *criContainerdService) portForward(id string, port int32, stream io.ReadWriteCloser) error {
|
||||
s, err := c.sandboxStore.Get(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find sandbox in store: %v", err)
|
||||
return fmt.Errorf("failed to find sandbox %q in store: %v", id, err)
|
||||
}
|
||||
t, err := s.Container.Task(context.Background(), nil)
|
||||
if err != nil {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
|
||||
cio "github.com/kubernetes-incubator/cri-containerd/pkg/server/io"
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
@ -92,12 +93,15 @@ func (c *Container) Delete() error {
|
||||
type Store struct {
|
||||
lock sync.RWMutex
|
||||
containers map[string]Container
|
||||
// TODO(random-liu): Add trunc index.
|
||||
idIndex *truncindex.TruncIndex
|
||||
}
|
||||
|
||||
// NewStore creates a container store.
|
||||
func NewStore() *Store {
|
||||
return &Store{containers: make(map[string]Container)}
|
||||
return &Store{
|
||||
containers: make(map[string]Container),
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Add a container into the store. Returns store.ErrAlreadyExist if the
|
||||
@ -108,6 +112,9 @@ func (s *Store) Add(c Container) error {
|
||||
if _, ok := s.containers[c.ID]; ok {
|
||||
return store.ErrAlreadyExist
|
||||
}
|
||||
if err := s.idIndex.Add(c.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
s.containers[c.ID] = c
|
||||
return nil
|
||||
}
|
||||
@ -117,6 +124,13 @@ func (s *Store) Add(c Container) error {
|
||||
func (s *Store) Get(id string) (Container, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
id, err := s.idIndex.Get(id)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
err = store.ErrNotExist
|
||||
}
|
||||
return Container{}, err
|
||||
}
|
||||
if c, ok := s.containers[id]; ok {
|
||||
return c, nil
|
||||
}
|
||||
@ -138,5 +152,12 @@ func (s *Store) List() []Container {
|
||||
func (s *Store) Delete(id string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
id, err := s.idIndex.Get(id)
|
||||
if err != nil {
|
||||
// Note: The idIndex.Delete and delete doesn't handle truncated index.
|
||||
// So we need to return if there are error.
|
||||
return
|
||||
}
|
||||
s.idIndex.Delete(id) // nolint: errcheck
|
||||
delete(s.containers, id)
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import (
|
||||
)
|
||||
|
||||
func TestContainerStore(t *testing.T) {
|
||||
ids := []string{"1", "2", "3"}
|
||||
metadatas := map[string]Metadata{
|
||||
"1": {
|
||||
ID: "1",
|
||||
@ -43,32 +42,44 @@ func TestContainerStore(t *testing.T) {
|
||||
ImageRef: "TestImage-1",
|
||||
LogPath: "/test/log/path/1",
|
||||
},
|
||||
"2": {
|
||||
ID: "2",
|
||||
Name: "Container-2",
|
||||
SandboxID: "Sandbox-2",
|
||||
"2abcd": {
|
||||
ID: "2abcd",
|
||||
Name: "Container-2abcd",
|
||||
SandboxID: "Sandbox-2abcd",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "TestPod-2",
|
||||
Name: "TestPod-2abcd",
|
||||
Attempt: 2,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-2",
|
||||
LogPath: "/test/log/path/2",
|
||||
},
|
||||
"3": {
|
||||
ID: "3",
|
||||
Name: "Container-3",
|
||||
SandboxID: "Sandbox-3",
|
||||
"4a333": {
|
||||
ID: "4a333",
|
||||
Name: "Container-4a333",
|
||||
SandboxID: "Sandbox-4a333",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "TestPod-3",
|
||||
Name: "TestPod-4a333",
|
||||
Attempt: 3,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-3",
|
||||
LogPath: "/test/log/path/3",
|
||||
},
|
||||
"4abcd": {
|
||||
ID: "4abcd",
|
||||
Name: "Container-4abcd",
|
||||
SandboxID: "Sandbox-4abcd",
|
||||
Config: &runtime.ContainerConfig{
|
||||
Metadata: &runtime.ContainerMetadata{
|
||||
Name: "TestPod-4abcd",
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
ImageRef: "TestImage-4abcd",
|
||||
},
|
||||
}
|
||||
statuses := map[string]Status{
|
||||
"1": {
|
||||
@ -80,29 +91,39 @@ func TestContainerStore(t *testing.T) {
|
||||
Reason: "TestReason-1",
|
||||
Message: "TestMessage-1",
|
||||
},
|
||||
"2": {
|
||||
"2abcd": {
|
||||
Pid: 2,
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
FinishedAt: time.Now().UnixNano(),
|
||||
ExitCode: 2,
|
||||
Reason: "TestReason-2",
|
||||
Message: "TestMessage-2",
|
||||
Reason: "TestReason-2abcd",
|
||||
Message: "TestMessage-2abcd",
|
||||
},
|
||||
"3": {
|
||||
"4a333": {
|
||||
Pid: 3,
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
FinishedAt: time.Now().UnixNano(),
|
||||
ExitCode: 3,
|
||||
Reason: "TestReason-3",
|
||||
Message: "TestMessage-3",
|
||||
Reason: "TestReason-4a333",
|
||||
Message: "TestMessage-4a333",
|
||||
Removing: true,
|
||||
},
|
||||
"4abcd": {
|
||||
Pid: 4,
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
StartedAt: time.Now().UnixNano(),
|
||||
FinishedAt: time.Now().UnixNano(),
|
||||
ExitCode: 4,
|
||||
Reason: "TestReason-4abcd",
|
||||
Message: "TestMessage-4abcd",
|
||||
Removing: true,
|
||||
},
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
containers := map[string]Container{}
|
||||
for _, id := range ids {
|
||||
for id := range metadatas {
|
||||
container, err := NewContainer(
|
||||
metadatas[id],
|
||||
WithFakeStatus(statuses[id]),
|
||||
@ -119,30 +140,36 @@ func TestContainerStore(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Logf("should be able to get container")
|
||||
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||
for id, c := range containers {
|
||||
got, err := s.Get(id)
|
||||
got, err := s.Get(genTruncIndex(id))
|
||||
assert.NoError(err)
|
||||
assert.Equal(c, got)
|
||||
}
|
||||
|
||||
t.Logf("should be able to list containers")
|
||||
cs := s.List()
|
||||
assert.Len(cs, 3)
|
||||
assert.Len(cs, len(containers))
|
||||
|
||||
cntrNum := len(containers)
|
||||
for testID, v := range containers {
|
||||
truncID := genTruncIndex(testID)
|
||||
|
||||
testID := "2"
|
||||
t.Logf("add should return already exists error for duplicated container")
|
||||
assert.Equal(store.ErrAlreadyExist, s.Add(containers[testID]))
|
||||
assert.Equal(store.ErrAlreadyExist, s.Add(v))
|
||||
|
||||
t.Logf("should be able to delete container")
|
||||
s.Delete(testID)
|
||||
s.Delete(truncID)
|
||||
cntrNum--
|
||||
cs = s.List()
|
||||
assert.Len(cs, 2)
|
||||
assert.Len(cs, cntrNum)
|
||||
|
||||
t.Logf("get should return not exist error after deletion")
|
||||
c, err := s.Get(testID)
|
||||
c, err := s.Get(truncID)
|
||||
assert.Equal(Container{}, c)
|
||||
assert.Equal(store.ErrNotExist, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithContainerIO(t *testing.T) {
|
||||
meta := Metadata{
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
@ -48,28 +49,41 @@ type Image struct {
|
||||
type Store struct {
|
||||
lock sync.RWMutex
|
||||
images map[string]Image
|
||||
// TODO(random-liu): Add trunc index.
|
||||
idIndex *truncindex.TruncIndex
|
||||
}
|
||||
|
||||
// NewStore creates an image store.
|
||||
func NewStore() *Store {
|
||||
return &Store{images: make(map[string]Image)}
|
||||
return &Store{
|
||||
images: make(map[string]Image),
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Add an image into the store.
|
||||
func (s *Store) Add(img Image) {
|
||||
func (s *Store) Add(img Image) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if _, err := s.idIndex.Get(img.ID); err != nil {
|
||||
if err != truncindex.ErrNotExist {
|
||||
return err
|
||||
}
|
||||
if err := s.idIndex.Add(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
i, ok := s.images[img.ID]
|
||||
if !ok {
|
||||
// If the image doesn't exist, add it.
|
||||
s.images[img.ID] = img
|
||||
return
|
||||
return nil
|
||||
}
|
||||
// Or else, merge the repo tags/digests.
|
||||
i.RepoTags = mergeStringSlices(i.RepoTags, img.RepoTags)
|
||||
i.RepoDigests = mergeStringSlices(i.RepoDigests, img.RepoDigests)
|
||||
s.images[img.ID] = i
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the image with specified id. Returns store.ErrNotExist if the
|
||||
@ -77,6 +91,13 @@ func (s *Store) Add(img Image) {
|
||||
func (s *Store) Get(id string) (Image, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
id, err := s.idIndex.Get(id)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
err = store.ErrNotExist
|
||||
}
|
||||
return Image{}, err
|
||||
}
|
||||
if i, ok := s.images[id]; ok {
|
||||
return i, nil
|
||||
}
|
||||
@ -98,6 +119,13 @@ func (s *Store) List() []Image {
|
||||
func (s *Store) Delete(id string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
id, err := s.idIndex.Get(id)
|
||||
if err != nil {
|
||||
// Note: The idIndex.Delete and delete doesn't handle truncated index.
|
||||
// So we need to return if there are error.
|
||||
return
|
||||
}
|
||||
s.idIndex.Delete(id) // nolint: errcheck
|
||||
delete(s.images, id)
|
||||
}
|
||||
|
||||
|
@ -35,22 +35,30 @@ func TestImageStore(t *testing.T) {
|
||||
Size: 10,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
"2": {
|
||||
ID: "2",
|
||||
ChainID: "test-chain-id-2",
|
||||
RepoTags: []string{"tag-2"},
|
||||
RepoDigests: []string{"digest-2"},
|
||||
"2abcd": {
|
||||
ID: "2abcd",
|
||||
ChainID: "test-chain-id-2abcd",
|
||||
RepoTags: []string{"tag-2abcd"},
|
||||
RepoDigests: []string{"digest-2abcd"},
|
||||
Size: 20,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
"3": {
|
||||
ID: "3",
|
||||
RepoTags: []string{"tag-3"},
|
||||
RepoDigests: []string{"digest-3"},
|
||||
ChainID: "test-chain-id-3",
|
||||
"4a333": {
|
||||
ID: "4a333",
|
||||
RepoTags: []string{"tag-4a333"},
|
||||
RepoDigests: []string{"digest-4a333"},
|
||||
ChainID: "test-chain-id-4a333",
|
||||
Size: 30,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
"4abcd": {
|
||||
ID: "4abcd",
|
||||
RepoTags: []string{"tag-4abcd"},
|
||||
RepoDigests: []string{"digest-4abcd"},
|
||||
ChainID: "test-chain-id-4abcd",
|
||||
Size: 40,
|
||||
Config: &imagespec.ImageConfig{},
|
||||
},
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
|
||||
@ -58,49 +66,62 @@ func TestImageStore(t *testing.T) {
|
||||
|
||||
t.Logf("should be able to add image")
|
||||
for _, img := range images {
|
||||
s.Add(img)
|
||||
err := s.Add(img)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
t.Logf("should be able to get image")
|
||||
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||
for id, img := range images {
|
||||
got, err := s.Get(id)
|
||||
got, err := s.Get(genTruncIndex(id))
|
||||
assert.NoError(err)
|
||||
assert.Equal(img, got)
|
||||
}
|
||||
|
||||
t.Logf("should be able to list images")
|
||||
imgs := s.List()
|
||||
assert.Len(imgs, 3)
|
||||
assert.Len(imgs, len(images))
|
||||
|
||||
imageNum := len(images)
|
||||
for testID, v := range images {
|
||||
truncID := genTruncIndex(testID)
|
||||
oldRepoTag := v.RepoTags[0]
|
||||
oldRepoDigest := v.RepoDigests[0]
|
||||
newRepoTag := oldRepoTag + "new"
|
||||
newRepoDigest := oldRepoDigest + "new"
|
||||
|
||||
testID := "2"
|
||||
t.Logf("should be able to add new repo tags/digests")
|
||||
newImg := images[testID]
|
||||
newImg.RepoTags = []string{"tag-new"}
|
||||
newImg.RepoDigests = []string{"digest-new"}
|
||||
s.Add(newImg)
|
||||
got, err := s.Get(testID)
|
||||
newImg := v
|
||||
newImg.RepoTags = []string{newRepoTag}
|
||||
newImg.RepoDigests = []string{newRepoDigest}
|
||||
err := s.Add(newImg)
|
||||
assert.NoError(err)
|
||||
got, err := s.Get(truncID)
|
||||
assert.NoError(err)
|
||||
assert.Len(got.RepoTags, 2)
|
||||
assert.Contains(got.RepoTags, "tag-2", "tag-new")
|
||||
assert.Contains(got.RepoTags, oldRepoTag, newRepoTag)
|
||||
assert.Len(got.RepoDigests, 2)
|
||||
assert.Contains(got.RepoDigests, "digest-2", "digest-new")
|
||||
assert.Contains(got.RepoDigests, oldRepoDigest, newRepoDigest)
|
||||
|
||||
t.Logf("should not be able to add duplicated repo tags/digests")
|
||||
s.Add(newImg)
|
||||
got, err = s.Get(testID)
|
||||
err = s.Add(newImg)
|
||||
assert.NoError(err)
|
||||
got, err = s.Get(truncID)
|
||||
assert.NoError(err)
|
||||
assert.Len(got.RepoTags, 2)
|
||||
assert.Contains(got.RepoTags, "tag-2", "tag-new")
|
||||
assert.Contains(got.RepoTags, oldRepoTag, newRepoTag)
|
||||
assert.Len(got.RepoDigests, 2)
|
||||
assert.Contains(got.RepoDigests, "digest-2", "digest-new")
|
||||
assert.Contains(got.RepoDigests, oldRepoDigest, newRepoDigest)
|
||||
|
||||
t.Logf("should be able to delete image")
|
||||
s.Delete(testID)
|
||||
s.Delete(truncID)
|
||||
imageNum--
|
||||
imgs = s.List()
|
||||
assert.Len(imgs, 2)
|
||||
assert.Len(imgs, imageNum)
|
||||
|
||||
t.Logf("get should return empty struct and ErrNotExist after deletion")
|
||||
img, err := s.Get(testID)
|
||||
img, err := s.Get(truncID)
|
||||
assert.Equal(Image{}, img)
|
||||
assert.Equal(store.ErrNotExist, err)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||
)
|
||||
@ -39,12 +40,15 @@ type Sandbox struct {
|
||||
type Store struct {
|
||||
lock sync.RWMutex
|
||||
sandboxes map[string]Sandbox
|
||||
// TODO(random-liu): Add trunc index.
|
||||
idIndex *truncindex.TruncIndex
|
||||
}
|
||||
|
||||
// NewStore creates a sandbox store.
|
||||
func NewStore() *Store {
|
||||
return &Store{sandboxes: make(map[string]Sandbox)}
|
||||
return &Store{
|
||||
sandboxes: make(map[string]Sandbox),
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Add a sandbox into the store.
|
||||
@ -54,6 +58,9 @@ func (s *Store) Add(sb Sandbox) error {
|
||||
if _, ok := s.sandboxes[sb.ID]; ok {
|
||||
return store.ErrAlreadyExist
|
||||
}
|
||||
if err := s.idIndex.Add(sb.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
s.sandboxes[sb.ID] = sb
|
||||
return nil
|
||||
}
|
||||
@ -63,6 +70,13 @@ func (s *Store) Add(sb Sandbox) error {
|
||||
func (s *Store) Get(id string) (Sandbox, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
id, err := s.idIndex.Get(id)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
err = store.ErrNotExist
|
||||
}
|
||||
return Sandbox{}, err
|
||||
}
|
||||
if sb, ok := s.sandboxes[id]; ok {
|
||||
return sb, nil
|
||||
}
|
||||
@ -84,5 +98,12 @@ func (s *Store) List() []Sandbox {
|
||||
func (s *Store) Delete(id string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
id, err := s.idIndex.Get(id)
|
||||
if err != nil {
|
||||
// Note: The idIndex.Delete and delete doesn't handle truncated index.
|
||||
// So we need to return if there are error.
|
||||
return
|
||||
}
|
||||
s.idIndex.Delete(id) // nolint: errcheck
|
||||
delete(s.sandboxes, id)
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import (
|
||||
)
|
||||
|
||||
func TestSandboxStore(t *testing.T) {
|
||||
ids := []string{"1", "2", "3"}
|
||||
metadatas := map[string]Metadata{
|
||||
"1": {
|
||||
ID: "1",
|
||||
@ -41,36 +40,49 @@ func TestSandboxStore(t *testing.T) {
|
||||
},
|
||||
NetNSPath: "TestNetNS-1",
|
||||
},
|
||||
"2": {
|
||||
ID: "2",
|
||||
Name: "Sandbox-2",
|
||||
"2abcd": {
|
||||
ID: "2abcd",
|
||||
Name: "Sandbox-2abcd",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "TestPod-2",
|
||||
Uid: "TestUid-2",
|
||||
Namespace: "TestNamespace-2",
|
||||
Name: "TestPod-2abcd",
|
||||
Uid: "TestUid-2abcd",
|
||||
Namespace: "TestNamespace-2abcd",
|
||||
Attempt: 2,
|
||||
},
|
||||
},
|
||||
NetNSPath: "TestNetNS-2",
|
||||
},
|
||||
"3": {
|
||||
ID: "3",
|
||||
Name: "Sandbox-3",
|
||||
"4a333": {
|
||||
ID: "4a333",
|
||||
Name: "Sandbox-4a333",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "TestPod-3",
|
||||
Uid: "TestUid-3",
|
||||
Namespace: "TestNamespace-3",
|
||||
Name: "TestPod-4a333",
|
||||
Uid: "TestUid-4a333",
|
||||
Namespace: "TestNamespace-4a333",
|
||||
Attempt: 3,
|
||||
},
|
||||
},
|
||||
NetNSPath: "TestNetNS-3",
|
||||
},
|
||||
"4abcd": {
|
||||
ID: "4abcd",
|
||||
Name: "Sandbox-4abcd",
|
||||
Config: &runtime.PodSandboxConfig{
|
||||
Metadata: &runtime.PodSandboxMetadata{
|
||||
Name: "TestPod-4abcd",
|
||||
Uid: "TestUid-4abcd",
|
||||
Namespace: "TestNamespace-4abcd",
|
||||
Attempt: 1,
|
||||
},
|
||||
},
|
||||
NetNSPath: "TestNetNS-4abcd",
|
||||
},
|
||||
}
|
||||
assert := assertlib.New(t)
|
||||
sandboxes := map[string]Sandbox{}
|
||||
for _, id := range ids {
|
||||
for id := range metadatas {
|
||||
sandboxes[id] = Sandbox{Metadata: metadatas[id]}
|
||||
}
|
||||
|
||||
@ -82,27 +94,33 @@ func TestSandboxStore(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Logf("should be able to get sandbox")
|
||||
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||
for id, sb := range sandboxes {
|
||||
got, err := s.Get(id)
|
||||
got, err := s.Get(genTruncIndex(id))
|
||||
assert.NoError(err)
|
||||
assert.Equal(sb, got)
|
||||
}
|
||||
|
||||
t.Logf("should be able to list sandboxes")
|
||||
sbs := s.List()
|
||||
assert.Len(sbs, 3)
|
||||
assert.Len(sbs, len(sandboxes))
|
||||
|
||||
sbNum := len(sandboxes)
|
||||
for testID, v := range sandboxes {
|
||||
truncID := genTruncIndex(testID)
|
||||
|
||||
testID := "2"
|
||||
t.Logf("add should return already exists error for duplicated sandbox")
|
||||
assert.Equal(store.ErrAlreadyExist, s.Add(sandboxes[testID]))
|
||||
assert.Equal(store.ErrAlreadyExist, s.Add(v))
|
||||
|
||||
t.Logf("should be able to delete sandbox")
|
||||
s.Delete(testID)
|
||||
s.Delete(truncID)
|
||||
sbNum--
|
||||
sbs = s.List()
|
||||
assert.Len(sbs, 2)
|
||||
assert.Len(sbs, sbNum)
|
||||
|
||||
t.Logf("get should return not exist error after deletion")
|
||||
sb, err := s.Get(testID)
|
||||
sb, err := s.Get(truncID)
|
||||
assert.Equal(Sandbox{}, sb)
|
||||
assert.Equal(store.ErrNotExist, err)
|
||||
}
|
||||
}
|
||||
|
@ -66,3 +66,4 @@ k8s.io/client-go 82aa063804cf055e16e8911250f888bc216e8b61
|
||||
k8s.io/kube-openapi abfc5fbe1cf87ee697db107fdfd24c32fe4397a8
|
||||
k8s.io/kubernetes d9bc7f0896091ba9879743fe4c9b27f352fe8289
|
||||
k8s.io/utils 4fe312863be2155a7b68acd2aff1c9221b24e68c
|
||||
github.com/tchap/go-patricia 5ad6cdb7538b0097d5598c7e57f0a24072adf7dc
|
||||
|
139
vendor/github.com/docker/docker/pkg/truncindex/truncindex.go
generated
vendored
Normal file
139
vendor/github.com/docker/docker/pkg/truncindex/truncindex.go
generated
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
// Package truncindex provides a general 'index tree', used by Docker
|
||||
// in order to be able to reference containers by only a few unambiguous
|
||||
// characters of their id.
|
||||
package truncindex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tchap/go-patricia/patricia"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyPrefix is an error returned if the prefix was empty.
|
||||
ErrEmptyPrefix = errors.New("Prefix can't be empty")
|
||||
|
||||
// ErrIllegalChar is returned when a space is in the ID
|
||||
ErrIllegalChar = errors.New("illegal character: ' '")
|
||||
|
||||
// ErrNotExist is returned when ID or its prefix not found in index.
|
||||
ErrNotExist = errors.New("ID does not exist")
|
||||
)
|
||||
|
||||
// ErrAmbiguousPrefix is returned if the prefix was ambiguous
|
||||
// (multiple ids for the prefix).
|
||||
type ErrAmbiguousPrefix struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (e ErrAmbiguousPrefix) Error() string {
|
||||
return fmt.Sprintf("Multiple IDs found with provided prefix: %s", e.prefix)
|
||||
}
|
||||
|
||||
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
|
||||
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
|
||||
type TruncIndex struct {
|
||||
sync.RWMutex
|
||||
trie *patricia.Trie
|
||||
ids map[string]struct{}
|
||||
}
|
||||
|
||||
// NewTruncIndex creates a new TruncIndex and initializes with a list of IDs.
|
||||
func NewTruncIndex(ids []string) (idx *TruncIndex) {
|
||||
idx = &TruncIndex{
|
||||
ids: make(map[string]struct{}),
|
||||
|
||||
// Change patricia max prefix per node length,
|
||||
// because our len(ID) always 64
|
||||
trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)),
|
||||
}
|
||||
for _, id := range ids {
|
||||
idx.addID(id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (idx *TruncIndex) addID(id string) error {
|
||||
if strings.Contains(id, " ") {
|
||||
return ErrIllegalChar
|
||||
}
|
||||
if id == "" {
|
||||
return ErrEmptyPrefix
|
||||
}
|
||||
if _, exists := idx.ids[id]; exists {
|
||||
return fmt.Errorf("id already exists: '%s'", id)
|
||||
}
|
||||
idx.ids[id] = struct{}{}
|
||||
if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted {
|
||||
return fmt.Errorf("failed to insert id: %s", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add adds a new ID to the TruncIndex.
|
||||
func (idx *TruncIndex) Add(id string) error {
|
||||
idx.Lock()
|
||||
defer idx.Unlock()
|
||||
return idx.addID(id)
|
||||
}
|
||||
|
||||
// Delete removes an ID from the TruncIndex. If there are multiple IDs
|
||||
// with the given prefix, an error is thrown.
|
||||
func (idx *TruncIndex) Delete(id string) error {
|
||||
idx.Lock()
|
||||
defer idx.Unlock()
|
||||
if _, exists := idx.ids[id]; !exists || id == "" {
|
||||
return fmt.Errorf("no such id: '%s'", id)
|
||||
}
|
||||
delete(idx.ids, id)
|
||||
if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted {
|
||||
return fmt.Errorf("no such id: '%s'", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves an ID from the TruncIndex. If there are multiple IDs
|
||||
// with the given prefix, an error is thrown.
|
||||
func (idx *TruncIndex) Get(s string) (string, error) {
|
||||
if s == "" {
|
||||
return "", ErrEmptyPrefix
|
||||
}
|
||||
var (
|
||||
id string
|
||||
)
|
||||
subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if id != "" {
|
||||
// we haven't found the ID if there are two or more IDs
|
||||
id = ""
|
||||
return ErrAmbiguousPrefix{prefix: string(prefix)}
|
||||
}
|
||||
id = string(prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
idx.RLock()
|
||||
defer idx.RUnlock()
|
||||
if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if id != "" {
|
||||
return id, nil
|
||||
}
|
||||
return "", ErrNotExist
|
||||
}
|
||||
|
||||
// Iterate iterates over all stored IDs and passes each of them to the given
|
||||
// handler. Take care that the handler method does not call any public
|
||||
// method on truncindex as the internal locking is not reentrant/recursive
|
||||
// and will result in deadlock.
|
||||
func (idx *TruncIndex) Iterate(handler func(id string)) {
|
||||
idx.Lock()
|
||||
defer idx.Unlock()
|
||||
idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
handler(string(prefix))
|
||||
return nil
|
||||
})
|
||||
}
|
20
vendor/github.com/tchap/go-patricia/LICENSE
generated
vendored
Normal file
20
vendor/github.com/tchap/go-patricia/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 The AUTHORS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
117
vendor/github.com/tchap/go-patricia/README.md
generated
vendored
Normal file
117
vendor/github.com/tchap/go-patricia/README.md
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
# go-patricia #
|
||||
|
||||
**Documentation**: [GoDoc](http://godoc.org/github.com/tchap/go-patricia/patricia)<br />
|
||||
**Test Coverage**: [](https://coveralls.io/r/tchap/go-patricia)
|
||||
|
||||
## About ##
|
||||
|
||||
A generic patricia trie (also called radix tree) implemented in Go (Golang).
|
||||
|
||||
The patricia trie as implemented in this library enables fast visiting of items
|
||||
in some particular ways:
|
||||
|
||||
1. visit all items saved in the tree,
|
||||
2. visit all items matching particular prefix (visit subtree), or
|
||||
3. given a string, visit all items matching some prefix of that string.
|
||||
|
||||
`[]byte` type is used for keys, `interface{}` for values.
|
||||
|
||||
`Trie` is not thread safe. Synchronize the access yourself.
|
||||
|
||||
### State of the Project ###
|
||||
|
||||
Apparently some people are using this, so the API should not change often.
|
||||
Any ideas on how to make the library better are still welcome.
|
||||
|
||||
More (unit) testing would be cool as well...
|
||||
|
||||
## Usage ##
|
||||
|
||||
Import the package from GitHub first.
|
||||
|
||||
```go
|
||||
import "github.com/tchap/go-patricia/patricia"
|
||||
```
|
||||
|
||||
You can as well use gopkg.in thingie:
|
||||
|
||||
```go
|
||||
import "gopkg.in/tchap/go-patricia.v2/patricia"
|
||||
```
|
||||
|
||||
Then you can start having fun.
|
||||
|
||||
```go
|
||||
printItem := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
fmt.Printf("%q: %v\n", prefix, item)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a new default trie (using the default parameter values).
|
||||
trie := NewTrie()
|
||||
|
||||
// Create a new custom trie.
|
||||
trie := NewTrie(MaxPrefixPerNode(16), MaxChildrenPerSparseNode(10))
|
||||
|
||||
// Insert some items.
|
||||
trie.Insert(Prefix("Pepa Novak"), 1)
|
||||
trie.Insert(Prefix("Pepa Sindelar"), 2)
|
||||
trie.Insert(Prefix("Karel Macha"), 3)
|
||||
trie.Insert(Prefix("Karel Hynek Macha"), 4)
|
||||
|
||||
// Just check if some things are present in the tree.
|
||||
key := Prefix("Pepa Novak")
|
||||
fmt.Printf("%q present? %v\n", key, trie.Match(key))
|
||||
// "Pepa Novak" present? true
|
||||
key = Prefix("Karel")
|
||||
fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key))
|
||||
// Anybody called "Karel" here? true
|
||||
|
||||
// Walk the tree in alphabetical order.
|
||||
trie.Visit(printItem)
|
||||
// "Karel Hynek Macha": 4
|
||||
// "Karel Macha": 3
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
|
||||
// Walk a subtree.
|
||||
trie.VisitSubtree(Prefix("Pepa"), printItem)
|
||||
// "Pepa Novak": 1
|
||||
// "Pepa Sindelar": 2
|
||||
|
||||
// Modify an item, then fetch it from the tree.
|
||||
trie.Set(Prefix("Karel Hynek Macha"), 10)
|
||||
key = Prefix("Karel Hynek Macha")
|
||||
fmt.Printf("%q: %v\n", key, trie.Get(key))
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Walk prefixes.
|
||||
prefix := Prefix("Karel Hynek Macha je kouzelnik")
|
||||
trie.VisitPrefixes(prefix, printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
|
||||
// Delete some items.
|
||||
trie.Delete(Prefix("Pepa Novak"))
|
||||
trie.Delete(Prefix("Karel Macha"))
|
||||
|
||||
// Walk again.
|
||||
trie.Visit(printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
// "Pepa Sindelar": 2
|
||||
|
||||
// Delete a subtree.
|
||||
trie.DeleteSubtree(Prefix("Pepa"))
|
||||
|
||||
// Print what is left.
|
||||
trie.Visit(printItem)
|
||||
// "Karel Hynek Macha": 10
|
||||
```
|
||||
|
||||
## License ##
|
||||
|
||||
MIT, check the `LICENSE` file.
|
||||
|
||||
[](https://www.gittip.com/tchap/
|
||||
"Gittip Badge")
|
325
vendor/github.com/tchap/go-patricia/patricia/children.go
generated
vendored
Normal file
325
vendor/github.com/tchap/go-patricia/patricia/children.go
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type childList interface {
|
||||
length() int
|
||||
head() *Trie
|
||||
add(child *Trie) childList
|
||||
remove(b byte)
|
||||
replace(b byte, child *Trie)
|
||||
next(b byte) *Trie
|
||||
walk(prefix *Prefix, visitor VisitorFunc) error
|
||||
print(w io.Writer, indent int)
|
||||
total() int
|
||||
}
|
||||
|
||||
type tries []*Trie
|
||||
|
||||
func (t tries) Len() int {
|
||||
return len(t)
|
||||
}
|
||||
|
||||
func (t tries) Less(i, j int) bool {
|
||||
strings := sort.StringSlice{string(t[i].prefix), string(t[j].prefix)}
|
||||
return strings.Less(0, 1)
|
||||
}
|
||||
|
||||
func (t tries) Swap(i, j int) {
|
||||
t[i], t[j] = t[j], t[i]
|
||||
}
|
||||
|
||||
type sparseChildList struct {
|
||||
children tries
|
||||
}
|
||||
|
||||
func newSparseChildList(maxChildrenPerSparseNode int) childList {
|
||||
return &sparseChildList{
|
||||
children: make(tries, 0, maxChildrenPerSparseNode),
|
||||
}
|
||||
}
|
||||
|
||||
func (list *sparseChildList) length() int {
|
||||
return len(list.children)
|
||||
}
|
||||
|
||||
func (list *sparseChildList) head() *Trie {
|
||||
return list.children[0]
|
||||
}
|
||||
|
||||
func (list *sparseChildList) add(child *Trie) childList {
|
||||
// Search for an empty spot and insert the child if possible.
|
||||
if len(list.children) != cap(list.children) {
|
||||
list.children = append(list.children, child)
|
||||
return list
|
||||
}
|
||||
|
||||
// Otherwise we have to transform to the dense list type.
|
||||
return newDenseChildList(list, child)
|
||||
}
|
||||
|
||||
func (list *sparseChildList) remove(b byte) {
|
||||
for i, node := range list.children {
|
||||
if node.prefix[0] == b {
|
||||
list.children[i] = list.children[len(list.children)-1]
|
||||
list.children[len(list.children)-1] = nil
|
||||
list.children = list.children[:len(list.children)-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This is not supposed to be reached.
|
||||
panic("removing non-existent child")
|
||||
}
|
||||
|
||||
func (list *sparseChildList) replace(b byte, child *Trie) {
|
||||
// Make a consistency check.
|
||||
if p0 := child.prefix[0]; p0 != b {
|
||||
panic(fmt.Errorf("child prefix mismatch: %v != %v", p0, b))
|
||||
}
|
||||
|
||||
// Seek the child and replace it.
|
||||
for i, node := range list.children {
|
||||
if node.prefix[0] == b {
|
||||
list.children[i] = child
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (list *sparseChildList) next(b byte) *Trie {
|
||||
for _, child := range list.children {
|
||||
if child.prefix[0] == b {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (list *sparseChildList) walk(prefix *Prefix, visitor VisitorFunc) error {
|
||||
|
||||
sort.Sort(list.children)
|
||||
|
||||
for _, child := range list.children {
|
||||
*prefix = append(*prefix, child.prefix...)
|
||||
if child.item != nil {
|
||||
err := visitor(*prefix, child.item)
|
||||
if err != nil {
|
||||
if err == SkipSubtree {
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
continue
|
||||
}
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := child.children.walk(prefix, visitor)
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (list *sparseChildList) total() int {
|
||||
tot := 0
|
||||
for _, child := range list.children {
|
||||
if child != nil {
|
||||
tot = tot + child.total()
|
||||
}
|
||||
}
|
||||
return tot
|
||||
}
|
||||
|
||||
func (list *sparseChildList) print(w io.Writer, indent int) {
|
||||
for _, child := range list.children {
|
||||
if child != nil {
|
||||
child.print(w, indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type denseChildList struct {
|
||||
min int
|
||||
max int
|
||||
numChildren int
|
||||
headIndex int
|
||||
children []*Trie
|
||||
}
|
||||
|
||||
func newDenseChildList(list *sparseChildList, child *Trie) childList {
|
||||
var (
|
||||
min int = 255
|
||||
max int = 0
|
||||
)
|
||||
for _, child := range list.children {
|
||||
b := int(child.prefix[0])
|
||||
if b < min {
|
||||
min = b
|
||||
}
|
||||
if b > max {
|
||||
max = b
|
||||
}
|
||||
}
|
||||
|
||||
b := int(child.prefix[0])
|
||||
if b < min {
|
||||
min = b
|
||||
}
|
||||
if b > max {
|
||||
max = b
|
||||
}
|
||||
|
||||
children := make([]*Trie, max-min+1)
|
||||
for _, child := range list.children {
|
||||
children[int(child.prefix[0])-min] = child
|
||||
}
|
||||
children[int(child.prefix[0])-min] = child
|
||||
|
||||
return &denseChildList{
|
||||
min: min,
|
||||
max: max,
|
||||
numChildren: list.length() + 1,
|
||||
headIndex: 0,
|
||||
children: children,
|
||||
}
|
||||
}
|
||||
|
||||
func (list *denseChildList) length() int {
|
||||
return list.numChildren
|
||||
}
|
||||
|
||||
func (list *denseChildList) head() *Trie {
|
||||
return list.children[list.headIndex]
|
||||
}
|
||||
|
||||
func (list *denseChildList) add(child *Trie) childList {
|
||||
b := int(child.prefix[0])
|
||||
var i int
|
||||
|
||||
switch {
|
||||
case list.min <= b && b <= list.max:
|
||||
if list.children[b-list.min] != nil {
|
||||
panic("dense child list collision detected")
|
||||
}
|
||||
i = b - list.min
|
||||
list.children[i] = child
|
||||
|
||||
case b < list.min:
|
||||
children := make([]*Trie, list.max-b+1)
|
||||
i = 0
|
||||
children[i] = child
|
||||
copy(children[list.min-b:], list.children)
|
||||
list.children = children
|
||||
list.min = b
|
||||
|
||||
default: // b > list.max
|
||||
children := make([]*Trie, b-list.min+1)
|
||||
i = b - list.min
|
||||
children[i] = child
|
||||
copy(children, list.children)
|
||||
list.children = children
|
||||
list.max = b
|
||||
}
|
||||
|
||||
list.numChildren++
|
||||
if i < list.headIndex {
|
||||
list.headIndex = i
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (list *denseChildList) remove(b byte) {
|
||||
i := int(b) - list.min
|
||||
if list.children[i] == nil {
|
||||
// This is not supposed to be reached.
|
||||
panic("removing non-existent child")
|
||||
}
|
||||
list.numChildren--
|
||||
list.children[i] = nil
|
||||
|
||||
// Update head index.
|
||||
if i == list.headIndex {
|
||||
for ; i < len(list.children); i++ {
|
||||
if list.children[i] != nil {
|
||||
list.headIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (list *denseChildList) replace(b byte, child *Trie) {
|
||||
// Make a consistency check.
|
||||
if p0 := child.prefix[0]; p0 != b {
|
||||
panic(fmt.Errorf("child prefix mismatch: %v != %v", p0, b))
|
||||
}
|
||||
|
||||
// Replace the child.
|
||||
list.children[int(b)-list.min] = child
|
||||
}
|
||||
|
||||
func (list *denseChildList) next(b byte) *Trie {
|
||||
i := int(b)
|
||||
if i < list.min || list.max < i {
|
||||
return nil
|
||||
}
|
||||
return list.children[i-list.min]
|
||||
}
|
||||
|
||||
func (list *denseChildList) walk(prefix *Prefix, visitor VisitorFunc) error {
|
||||
for _, child := range list.children {
|
||||
if child == nil {
|
||||
continue
|
||||
}
|
||||
*prefix = append(*prefix, child.prefix...)
|
||||
if child.item != nil {
|
||||
if err := visitor(*prefix, child.item); err != nil {
|
||||
if err == SkipSubtree {
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
continue
|
||||
}
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := child.children.walk(prefix, visitor)
|
||||
*prefix = (*prefix)[:len(*prefix)-len(child.prefix)]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (list *denseChildList) print(w io.Writer, indent int) {
|
||||
for _, child := range list.children {
|
||||
if child != nil {
|
||||
child.print(w, indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (list *denseChildList) total() int {
|
||||
tot := 0
|
||||
for _, child := range list.children {
|
||||
if child != nil {
|
||||
tot = tot + child.total()
|
||||
}
|
||||
}
|
||||
return tot
|
||||
}
|
594
vendor/github.com/tchap/go-patricia/patricia/patricia.go
generated
vendored
Normal file
594
vendor/github.com/tchap/go-patricia/patricia/patricia.go
generated
vendored
Normal file
@ -0,0 +1,594 @@
|
||||
// Copyright (c) 2014 The go-patricia AUTHORS
|
||||
//
|
||||
// Use of this source code is governed by The MIT License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package patricia
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Trie
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
DefaultMaxPrefixPerNode = 10
|
||||
DefaultMaxChildrenPerSparseNode = 8
|
||||
)
|
||||
|
||||
type (
|
||||
Prefix []byte
|
||||
Item interface{}
|
||||
VisitorFunc func(prefix Prefix, item Item) error
|
||||
)
|
||||
|
||||
// Trie is a generic patricia trie that allows fast retrieval of items by prefix.
|
||||
// and other funky stuff.
|
||||
//
|
||||
// Trie is not thread-safe.
|
||||
type Trie struct {
|
||||
prefix Prefix
|
||||
item Item
|
||||
|
||||
maxPrefixPerNode int
|
||||
maxChildrenPerSparseNode int
|
||||
|
||||
children childList
|
||||
}
|
||||
|
||||
// Public API ------------------------------------------------------------------
|
||||
|
||||
type Option func(*Trie)
|
||||
|
||||
// Trie constructor.
|
||||
func NewTrie(options ...Option) *Trie {
|
||||
trie := &Trie{}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(trie)
|
||||
}
|
||||
|
||||
if trie.maxPrefixPerNode <= 0 {
|
||||
trie.maxPrefixPerNode = DefaultMaxPrefixPerNode
|
||||
}
|
||||
if trie.maxChildrenPerSparseNode <= 0 {
|
||||
trie.maxChildrenPerSparseNode = DefaultMaxChildrenPerSparseNode
|
||||
}
|
||||
|
||||
trie.children = newSparseChildList(trie.maxChildrenPerSparseNode)
|
||||
return trie
|
||||
}
|
||||
|
||||
func MaxPrefixPerNode(value int) Option {
|
||||
return func(trie *Trie) {
|
||||
trie.maxPrefixPerNode = value
|
||||
}
|
||||
}
|
||||
|
||||
func MaxChildrenPerSparseNode(value int) Option {
|
||||
return func(trie *Trie) {
|
||||
trie.maxChildrenPerSparseNode = value
|
||||
}
|
||||
}
|
||||
|
||||
// Item returns the item stored in the root of this trie.
|
||||
func (trie *Trie) Item() Item {
|
||||
return trie.item
|
||||
}
|
||||
|
||||
// Insert inserts a new item into the trie using the given prefix. Insert does
|
||||
// not replace existing items. It returns false if an item was already in place.
|
||||
func (trie *Trie) Insert(key Prefix, item Item) (inserted bool) {
|
||||
return trie.put(key, item, false)
|
||||
}
|
||||
|
||||
// Set works much like Insert, but it always sets the item, possibly replacing
|
||||
// the item previously inserted.
|
||||
func (trie *Trie) Set(key Prefix, item Item) {
|
||||
trie.put(key, item, true)
|
||||
}
|
||||
|
||||
// Get returns the item located at key.
|
||||
//
|
||||
// This method is a bit dangerous, because Get can as well end up in an internal
|
||||
// node that is not really representing any user-defined value. So when nil is
|
||||
// a valid value being used, it is not possible to tell if the value was inserted
|
||||
// into the tree by the user or not. A possible workaround for this is not to use
|
||||
// nil interface as a valid value, even using zero value of any type is enough
|
||||
// to prevent this bad behaviour.
|
||||
func (trie *Trie) Get(key Prefix) (item Item) {
|
||||
_, node, found, leftover := trie.findSubtree(key)
|
||||
if !found || len(leftover) != 0 {
|
||||
return nil
|
||||
}
|
||||
return node.item
|
||||
}
|
||||
|
||||
// Match returns what Get(prefix) != nil would return. The same warning as for
|
||||
// Get applies here as well.
|
||||
func (trie *Trie) Match(prefix Prefix) (matchedExactly bool) {
|
||||
return trie.Get(prefix) != nil
|
||||
}
|
||||
|
||||
// MatchSubtree returns true when there is a subtree representing extensions
|
||||
// to key, that is if there are any keys in the tree which have key as prefix.
|
||||
func (trie *Trie) MatchSubtree(key Prefix) (matched bool) {
|
||||
_, _, matched, _ = trie.findSubtree(key)
|
||||
return
|
||||
}
|
||||
|
||||
// Visit calls visitor on every node containing a non-nil item
|
||||
// in alphabetical order.
|
||||
//
|
||||
// If an error is returned from visitor, the function stops visiting the tree
|
||||
// and returns that error, unless it is a special error - SkipSubtree. In that
|
||||
// case Visit skips the subtree represented by the current node and continues
|
||||
// elsewhere.
|
||||
func (trie *Trie) Visit(visitor VisitorFunc) error {
|
||||
return trie.walk(nil, visitor)
|
||||
}
|
||||
|
||||
func (trie *Trie) size() int {
|
||||
n := 0
|
||||
|
||||
trie.walk(nil, func(prefix Prefix, item Item) error {
|
||||
n++
|
||||
return nil
|
||||
})
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (trie *Trie) total() int {
|
||||
return 1 + trie.children.total()
|
||||
}
|
||||
|
||||
// VisitSubtree works much like Visit, but it only visits nodes matching prefix.
|
||||
func (trie *Trie) VisitSubtree(prefix Prefix, visitor VisitorFunc) error {
|
||||
// Nil prefix not allowed.
|
||||
if prefix == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Locate the relevant subtree.
|
||||
_, root, found, leftover := trie.findSubtree(prefix)
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
prefix = append(prefix, leftover...)
|
||||
|
||||
// Visit it.
|
||||
return root.walk(prefix, visitor)
|
||||
}
|
||||
|
||||
// VisitPrefixes visits only nodes that represent prefixes of key.
|
||||
// To say the obvious, returning SkipSubtree from visitor makes no sense here.
|
||||
func (trie *Trie) VisitPrefixes(key Prefix, visitor VisitorFunc) error {
|
||||
// Nil key not allowed.
|
||||
if key == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk the path matching key prefixes.
|
||||
node := trie
|
||||
prefix := key
|
||||
offset := 0
|
||||
for {
|
||||
// Compute what part of prefix matches.
|
||||
common := node.longestCommonPrefixLength(key)
|
||||
key = key[common:]
|
||||
offset += common
|
||||
|
||||
// Partial match means that there is no subtree matching prefix.
|
||||
if common < len(node.prefix) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call the visitor.
|
||||
if item := node.item; item != nil {
|
||||
if err := visitor(prefix[:offset], item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(key) == 0 {
|
||||
// This node represents key, we are finished.
|
||||
return nil
|
||||
}
|
||||
|
||||
// There is some key suffix left, move to the children.
|
||||
child := node.children.next(key[0])
|
||||
if child == nil {
|
||||
// There is nowhere to continue, return.
|
||||
return nil
|
||||
}
|
||||
|
||||
node = child
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the item represented by the given prefix.
|
||||
//
|
||||
// True is returned if the matching node was found and deleted.
|
||||
func (trie *Trie) Delete(key Prefix) (deleted bool) {
|
||||
// Nil prefix not allowed.
|
||||
if key == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Find the relevant node.
|
||||
path, found, _ := trie.findSubtreePath(key)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
node := path[len(path)-1]
|
||||
var parent *Trie
|
||||
if len(path) != 1 {
|
||||
parent = path[len(path)-2]
|
||||
}
|
||||
|
||||
// If the item is already set to nil, there is nothing to do.
|
||||
if node.item == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Delete the item.
|
||||
node.item = nil
|
||||
|
||||
// Initialise i before goto.
|
||||
// Will be used later in a loop.
|
||||
i := len(path) - 1
|
||||
|
||||
// In case there are some child nodes, we cannot drop the whole subtree.
|
||||
// We can try to compact nodes, though.
|
||||
if node.children.length() != 0 {
|
||||
goto Compact
|
||||
}
|
||||
|
||||
// In case we are at the root, just reset it and we are done.
|
||||
if parent == nil {
|
||||
node.reset()
|
||||
return true
|
||||
}
|
||||
|
||||
// We can drop a subtree.
|
||||
// Find the first ancestor that has its value set or it has 2 or more child nodes.
|
||||
// That will be the node where to drop the subtree at.
|
||||
for ; i >= 0; i-- {
|
||||
if current := path[i]; current.item != nil || current.children.length() >= 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case when there is no such node.
|
||||
// In other words, we can reset the whole tree.
|
||||
if i == -1 {
|
||||
path[0].reset()
|
||||
return true
|
||||
}
|
||||
|
||||
// We can just remove the subtree here.
|
||||
node = path[i]
|
||||
if i == 0 {
|
||||
parent = nil
|
||||
} else {
|
||||
parent = path[i-1]
|
||||
}
|
||||
// i+1 is always a valid index since i is never pointing to the last node.
|
||||
// The loop above skips at least the last node since we are sure that the item
|
||||
// is set to nil and it has no children, othewise we would be compacting instead.
|
||||
node.children.remove(path[i+1].prefix[0])
|
||||
|
||||
Compact:
|
||||
// The node is set to the first non-empty ancestor,
|
||||
// so try to compact since that might be possible now.
|
||||
if compacted := node.compact(); compacted != node {
|
||||
if parent == nil {
|
||||
*node = *compacted
|
||||
} else {
|
||||
parent.children.replace(node.prefix[0], compacted)
|
||||
*parent = *parent.compact()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteSubtree finds the subtree exactly matching prefix and deletes it.
|
||||
//
|
||||
// True is returned if the subtree was found and deleted.
|
||||
func (trie *Trie) DeleteSubtree(prefix Prefix) (deleted bool) {
|
||||
// Nil prefix not allowed.
|
||||
if prefix == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
// Empty trie must be handled explicitly.
|
||||
if trie.prefix == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Locate the relevant subtree.
|
||||
parent, root, found, _ := trie.findSubtree(prefix)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we are in the root of the trie, reset the trie.
|
||||
if parent == nil {
|
||||
root.reset()
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise remove the root node from its parent.
|
||||
parent.children.remove(root.prefix[0])
|
||||
return true
|
||||
}
|
||||
|
||||
// Internal helper methods -----------------------------------------------------
|
||||
|
||||
func (trie *Trie) empty() bool {
|
||||
return trie.item == nil && trie.children.length() == 0
|
||||
}
|
||||
|
||||
func (trie *Trie) reset() {
|
||||
trie.prefix = nil
|
||||
trie.children = newSparseChildList(trie.maxPrefixPerNode)
|
||||
}
|
||||
|
||||
func (trie *Trie) put(key Prefix, item Item, replace bool) (inserted bool) {
|
||||
// Nil prefix not allowed.
|
||||
if key == nil {
|
||||
panic(ErrNilPrefix)
|
||||
}
|
||||
|
||||
var (
|
||||
common int
|
||||
node *Trie = trie
|
||||
child *Trie
|
||||
)
|
||||
|
||||
if node.prefix == nil {
|
||||
if len(key) <= trie.maxPrefixPerNode {
|
||||
node.prefix = key
|
||||
goto InsertItem
|
||||
}
|
||||
node.prefix = key[:trie.maxPrefixPerNode]
|
||||
key = key[trie.maxPrefixPerNode:]
|
||||
goto AppendChild
|
||||
}
|
||||
|
||||
for {
|
||||
// Compute the longest common prefix length.
|
||||
common = node.longestCommonPrefixLength(key)
|
||||
key = key[common:]
|
||||
|
||||
// Only a part matches, split.
|
||||
if common < len(node.prefix) {
|
||||
goto SplitPrefix
|
||||
}
|
||||
|
||||
// common == len(node.prefix) since never (common > len(node.prefix))
|
||||
// common == len(former key) <-> 0 == len(key)
|
||||
// -> former key == node.prefix
|
||||
if len(key) == 0 {
|
||||
goto InsertItem
|
||||
}
|
||||
|
||||
// Check children for matching prefix.
|
||||
child = node.children.next(key[0])
|
||||
if child == nil {
|
||||
goto AppendChild
|
||||
}
|
||||
node = child
|
||||
}
|
||||
|
||||
SplitPrefix:
|
||||
// Split the prefix if necessary.
|
||||
child = new(Trie)
|
||||
*child = *node
|
||||
*node = *NewTrie()
|
||||
node.prefix = child.prefix[:common]
|
||||
child.prefix = child.prefix[common:]
|
||||
child = child.compact()
|
||||
node.children = node.children.add(child)
|
||||
|
||||
AppendChild:
|
||||
// Keep appending children until whole prefix is inserted.
|
||||
// This loop starts with empty node.prefix that needs to be filled.
|
||||
for len(key) != 0 {
|
||||
child := NewTrie()
|
||||
if len(key) <= trie.maxPrefixPerNode {
|
||||
child.prefix = key
|
||||
node.children = node.children.add(child)
|
||||
node = child
|
||||
goto InsertItem
|
||||
} else {
|
||||
child.prefix = key[:trie.maxPrefixPerNode]
|
||||
key = key[trie.maxPrefixPerNode:]
|
||||
node.children = node.children.add(child)
|
||||
node = child
|
||||
}
|
||||
}
|
||||
|
||||
InsertItem:
|
||||
// Try to insert the item if possible.
|
||||
if replace || node.item == nil {
|
||||
node.item = item
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (trie *Trie) compact() *Trie {
|
||||
// Only a node with a single child can be compacted.
|
||||
if trie.children.length() != 1 {
|
||||
return trie
|
||||
}
|
||||
|
||||
child := trie.children.head()
|
||||
|
||||
// If any item is set, we cannot compact since we want to retain
|
||||
// the ability to do searching by key. This makes compaction less usable,
|
||||
// but that simply cannot be avoided.
|
||||
if trie.item != nil || child.item != nil {
|
||||
return trie
|
||||
}
|
||||
|
||||
// Make sure the combined prefixes fit into a single node.
|
||||
if len(trie.prefix)+len(child.prefix) > trie.maxPrefixPerNode {
|
||||
return trie
|
||||
}
|
||||
|
||||
// Concatenate the prefixes, move the items.
|
||||
child.prefix = append(trie.prefix, child.prefix...)
|
||||
if trie.item != nil {
|
||||
child.item = trie.item
|
||||
}
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (trie *Trie) findSubtree(prefix Prefix) (parent *Trie, root *Trie, found bool, leftover Prefix) {
|
||||
// Find the subtree matching prefix.
|
||||
root = trie
|
||||
for {
|
||||
// Compute what part of prefix matches.
|
||||
common := root.longestCommonPrefixLength(prefix)
|
||||
prefix = prefix[common:]
|
||||
|
||||
// We used up the whole prefix, subtree found.
|
||||
if len(prefix) == 0 {
|
||||
found = true
|
||||
leftover = root.prefix[common:]
|
||||
return
|
||||
}
|
||||
|
||||
// Partial match means that there is no subtree matching prefix.
|
||||
if common < len(root.prefix) {
|
||||
leftover = root.prefix[common:]
|
||||
return
|
||||
}
|
||||
|
||||
// There is some prefix left, move to the children.
|
||||
child := root.children.next(prefix[0])
|
||||
if child == nil {
|
||||
// There is nowhere to continue, there is no subtree matching prefix.
|
||||
return
|
||||
}
|
||||
|
||||
parent = root
|
||||
root = child
|
||||
}
|
||||
}
|
||||
|
||||
func (trie *Trie) findSubtreePath(prefix Prefix) (path []*Trie, found bool, leftover Prefix) {
|
||||
// Find the subtree matching prefix.
|
||||
root := trie
|
||||
var subtreePath []*Trie
|
||||
for {
|
||||
// Append the current root to the path.
|
||||
subtreePath = append(subtreePath, root)
|
||||
|
||||
// Compute what part of prefix matches.
|
||||
common := root.longestCommonPrefixLength(prefix)
|
||||
prefix = prefix[common:]
|
||||
|
||||
// We used up the whole prefix, subtree found.
|
||||
if len(prefix) == 0 {
|
||||
path = subtreePath
|
||||
found = true
|
||||
leftover = root.prefix[common:]
|
||||
return
|
||||
}
|
||||
|
||||
// Partial match means that there is no subtree matching prefix.
|
||||
if common < len(root.prefix) {
|
||||
leftover = root.prefix[common:]
|
||||
return
|
||||
}
|
||||
|
||||
// There is some prefix left, move to the children.
|
||||
child := root.children.next(prefix[0])
|
||||
if child == nil {
|
||||
// There is nowhere to continue, there is no subtree matching prefix.
|
||||
return
|
||||
}
|
||||
|
||||
root = child
|
||||
}
|
||||
}
|
||||
|
||||
func (trie *Trie) walk(actualRootPrefix Prefix, visitor VisitorFunc) error {
|
||||
var prefix Prefix
|
||||
// Allocate a bit more space for prefix at the beginning.
|
||||
if actualRootPrefix == nil {
|
||||
prefix = make(Prefix, 32+len(trie.prefix))
|
||||
copy(prefix, trie.prefix)
|
||||
prefix = prefix[:len(trie.prefix)]
|
||||
} else {
|
||||
prefix = make(Prefix, 32+len(actualRootPrefix))
|
||||
copy(prefix, actualRootPrefix)
|
||||
prefix = prefix[:len(actualRootPrefix)]
|
||||
}
|
||||
|
||||
// Visit the root first. Not that this works for empty trie as well since
|
||||
// in that case item == nil && len(children) == 0.
|
||||
if trie.item != nil {
|
||||
if err := visitor(prefix, trie.item); err != nil {
|
||||
if err == SkipSubtree {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Then continue to the children.
|
||||
return trie.children.walk(&prefix, visitor)
|
||||
}
|
||||
|
||||
func (trie *Trie) longestCommonPrefixLength(prefix Prefix) (i int) {
|
||||
for ; i < len(prefix) && i < len(trie.prefix) && prefix[i] == trie.prefix[i]; i++ {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (trie *Trie) dump() string {
|
||||
writer := &bytes.Buffer{}
|
||||
trie.print(writer, 0)
|
||||
return writer.String()
|
||||
}
|
||||
|
||||
func (trie *Trie) print(writer io.Writer, indent int) {
|
||||
fmt.Fprintf(writer, "%s%s %v\n", strings.Repeat(" ", indent), string(trie.prefix), trie.item)
|
||||
trie.children.print(writer, indent+2)
|
||||
}
|
||||
|
||||
// Errors ----------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
SkipSubtree = errors.New("Skip this subtree")
|
||||
ErrNilPrefix = errors.New("Nil prefix passed into a method call")
|
||||
)
|
Loading…
Reference in New Issue
Block a user