parent
0e6e593481
commit
5ee3423820
@ -52,7 +52,7 @@ func (c *criContainerdService) attachContainer(ctx context.Context, id string, s
|
|||||||
// Get container from our container store.
|
// Get container from our container store.
|
||||||
cntr, err := c.containerStore.Get(id)
|
cntr, err := c.containerStore.Get(id)
|
||||||
if err != nil {
|
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
|
id = cntr.ID
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
func (c *criContainerdService) Exec(ctx context.Context, r *runtime.ExecRequest) (*runtime.ExecResponse, error) {
|
func (c *criContainerdService) Exec(ctx context.Context, r *runtime.ExecRequest) (*runtime.ExecResponse, error) {
|
||||||
cntr, err := c.containerStore.Get(r.GetContainerId())
|
cntr, err := c.containerStore.Get(r.GetContainerId())
|
||||||
if err != nil {
|
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()
|
state := cntr.Status.Get().State()
|
||||||
if state != runtime.ContainerState_CONTAINER_RUNNING {
|
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.
|
// Get container from our container store.
|
||||||
cntr, err := c.containerStore.Get(id)
|
cntr, err := c.containerStore.Get(id)
|
||||||
if err != nil {
|
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
|
id = cntr.ID
|
||||||
|
|
||||||
|
@ -142,7 +142,9 @@ func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullIma
|
|||||||
img.RepoTags = []string{repoTag}
|
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
|
// 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
|
// 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)
|
return fmt.Errorf("failed to load images: %v", err)
|
||||||
}
|
}
|
||||||
for _, image := range images {
|
for _, image := range images {
|
||||||
c.imageStore.Add(image)
|
|
||||||
glog.V(4).Infof("Loaded image %+v", 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,
|
// 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.
|
// TODO(random-liu): Run a socat container inside the sandbox to do portforward.
|
||||||
sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
|
sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
|
||||||
if err != nil {
|
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)
|
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 {
|
func (c *criContainerdService) portForward(id string, port int32, stream io.ReadWriteCloser) error {
|
||||||
s, err := c.sandboxStore.Get(id)
|
s, err := c.sandboxStore.Get(id)
|
||||||
if err != nil {
|
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)
|
t, err := s.Container.Task(context.Background(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/docker/docker/pkg/truncindex"
|
||||||
|
|
||||||
cio "github.com/kubernetes-incubator/cri-containerd/pkg/server/io"
|
cio "github.com/kubernetes-incubator/cri-containerd/pkg/server/io"
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||||
@ -92,7 +93,7 @@ func (c *Container) Delete() error {
|
|||||||
type Store struct {
|
type Store struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
containers map[string]Container
|
containers map[string]Container
|
||||||
// TODO(random-liu): Add trunc index.
|
idIndex *truncindex.TruncIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadStore loads containers from runtime.
|
// LoadStore loads containers from runtime.
|
||||||
@ -101,7 +102,10 @@ func LoadStore() *Store { return nil }
|
|||||||
|
|
||||||
// NewStore creates a container store.
|
// NewStore creates a container store.
|
||||||
func NewStore() *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
|
// Add a container into the store. Returns store.ErrAlreadyExist if the
|
||||||
@ -112,6 +116,9 @@ func (s *Store) Add(c Container) error {
|
|||||||
if _, ok := s.containers[c.ID]; ok {
|
if _, ok := s.containers[c.ID]; ok {
|
||||||
return store.ErrAlreadyExist
|
return store.ErrAlreadyExist
|
||||||
}
|
}
|
||||||
|
if err := s.idIndex.Add(c.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.containers[c.ID] = c
|
s.containers[c.ID] = c
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -121,6 +128,13 @@ func (s *Store) Add(c Container) error {
|
|||||||
func (s *Store) Get(id string) (Container, error) {
|
func (s *Store) Get(id string) (Container, error) {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
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 {
|
if c, ok := s.containers[id]; ok {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
@ -142,5 +156,12 @@ func (s *Store) List() []Container {
|
|||||||
func (s *Store) Delete(id string) {
|
func (s *Store) Delete(id string) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
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)
|
delete(s.containers, id)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestContainerStore(t *testing.T) {
|
func TestContainerStore(t *testing.T) {
|
||||||
ids := []string{"1", "2", "3"}
|
|
||||||
metadatas := map[string]Metadata{
|
metadatas := map[string]Metadata{
|
||||||
"1": {
|
"1": {
|
||||||
ID: "1",
|
ID: "1",
|
||||||
@ -43,32 +42,44 @@ func TestContainerStore(t *testing.T) {
|
|||||||
ImageRef: "TestImage-1",
|
ImageRef: "TestImage-1",
|
||||||
LogPath: "/test/log/path/1",
|
LogPath: "/test/log/path/1",
|
||||||
},
|
},
|
||||||
"2": {
|
"2abcd": {
|
||||||
ID: "2",
|
ID: "2abcd",
|
||||||
Name: "Container-2",
|
Name: "Container-2abcd",
|
||||||
SandboxID: "Sandbox-2",
|
SandboxID: "Sandbox-2abcd",
|
||||||
Config: &runtime.ContainerConfig{
|
Config: &runtime.ContainerConfig{
|
||||||
Metadata: &runtime.ContainerMetadata{
|
Metadata: &runtime.ContainerMetadata{
|
||||||
Name: "TestPod-2",
|
Name: "TestPod-2abcd",
|
||||||
Attempt: 2,
|
Attempt: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImageRef: "TestImage-2",
|
ImageRef: "TestImage-2",
|
||||||
LogPath: "/test/log/path/2",
|
LogPath: "/test/log/path/2",
|
||||||
},
|
},
|
||||||
"3": {
|
"4a333": {
|
||||||
ID: "3",
|
ID: "4a333",
|
||||||
Name: "Container-3",
|
Name: "Container-4a333",
|
||||||
SandboxID: "Sandbox-3",
|
SandboxID: "Sandbox-4a333",
|
||||||
Config: &runtime.ContainerConfig{
|
Config: &runtime.ContainerConfig{
|
||||||
Metadata: &runtime.ContainerMetadata{
|
Metadata: &runtime.ContainerMetadata{
|
||||||
Name: "TestPod-3",
|
Name: "TestPod-4a333",
|
||||||
Attempt: 3,
|
Attempt: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImageRef: "TestImage-3",
|
ImageRef: "TestImage-3",
|
||||||
LogPath: "/test/log/path/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{
|
statuses := map[string]Status{
|
||||||
"1": {
|
"1": {
|
||||||
@ -80,29 +91,39 @@ func TestContainerStore(t *testing.T) {
|
|||||||
Reason: "TestReason-1",
|
Reason: "TestReason-1",
|
||||||
Message: "TestMessage-1",
|
Message: "TestMessage-1",
|
||||||
},
|
},
|
||||||
"2": {
|
"2abcd": {
|
||||||
Pid: 2,
|
Pid: 2,
|
||||||
CreatedAt: time.Now().UnixNano(),
|
CreatedAt: time.Now().UnixNano(),
|
||||||
StartedAt: time.Now().UnixNano(),
|
StartedAt: time.Now().UnixNano(),
|
||||||
FinishedAt: time.Now().UnixNano(),
|
FinishedAt: time.Now().UnixNano(),
|
||||||
ExitCode: 2,
|
ExitCode: 2,
|
||||||
Reason: "TestReason-2",
|
Reason: "TestReason-2abcd",
|
||||||
Message: "TestMessage-2",
|
Message: "TestMessage-2abcd",
|
||||||
},
|
},
|
||||||
"3": {
|
"4a333": {
|
||||||
Pid: 3,
|
Pid: 3,
|
||||||
CreatedAt: time.Now().UnixNano(),
|
CreatedAt: time.Now().UnixNano(),
|
||||||
StartedAt: time.Now().UnixNano(),
|
StartedAt: time.Now().UnixNano(),
|
||||||
FinishedAt: time.Now().UnixNano(),
|
FinishedAt: time.Now().UnixNano(),
|
||||||
ExitCode: 3,
|
ExitCode: 3,
|
||||||
Reason: "TestReason-3",
|
Reason: "TestReason-4a333",
|
||||||
Message: "TestMessage-3",
|
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,
|
Removing: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert := assertlib.New(t)
|
assert := assertlib.New(t)
|
||||||
containers := map[string]Container{}
|
containers := map[string]Container{}
|
||||||
for _, id := range ids {
|
for id := range metadatas {
|
||||||
container, err := NewContainer(
|
container, err := NewContainer(
|
||||||
metadatas[id],
|
metadatas[id],
|
||||||
WithFakeStatus(statuses[id]),
|
WithFakeStatus(statuses[id]),
|
||||||
@ -119,29 +140,35 @@ func TestContainerStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("should be able to get container")
|
t.Logf("should be able to get container")
|
||||||
|
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||||
for id, c := range containers {
|
for id, c := range containers {
|
||||||
got, err := s.Get(id)
|
got, err := s.Get(genTruncIndex(id))
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(c, got)
|
assert.Equal(c, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("should be able to list containers")
|
t.Logf("should be able to list containers")
|
||||||
cs := s.List()
|
cs := s.List()
|
||||||
assert.Len(cs, 3)
|
assert.Len(cs, len(containers))
|
||||||
|
|
||||||
testID := "2"
|
cntrNum := len(containers)
|
||||||
t.Logf("add should return already exists error for duplicated container")
|
for testID, v := range containers {
|
||||||
assert.Equal(store.ErrAlreadyExist, s.Add(containers[testID]))
|
truncID := genTruncIndex(testID)
|
||||||
|
|
||||||
t.Logf("should be able to delete container")
|
t.Logf("add should return already exists error for duplicated container")
|
||||||
s.Delete(testID)
|
assert.Equal(store.ErrAlreadyExist, s.Add(v))
|
||||||
cs = s.List()
|
|
||||||
assert.Len(cs, 2)
|
|
||||||
|
|
||||||
t.Logf("get should return not exist error after deletion")
|
t.Logf("should be able to delete container")
|
||||||
c, err := s.Get(testID)
|
s.Delete(truncID)
|
||||||
assert.Equal(Container{}, c)
|
cntrNum--
|
||||||
assert.Equal(store.ErrNotExist, err)
|
cs = s.List()
|
||||||
|
assert.Len(cs, cntrNum)
|
||||||
|
|
||||||
|
t.Logf("get should return not exist error after deletion")
|
||||||
|
c, err := s.Get(truncID)
|
||||||
|
assert.Equal(Container{}, c)
|
||||||
|
assert.Equal(store.ErrNotExist, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithContainerIO(t *testing.T) {
|
func TestWithContainerIO(t *testing.T) {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/docker/docker/pkg/truncindex"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||||
@ -46,9 +47,9 @@ type Image struct {
|
|||||||
|
|
||||||
// Store stores all images.
|
// Store stores all images.
|
||||||
type Store struct {
|
type Store struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
images map[string]Image
|
images map[string]Image
|
||||||
// TODO(random-liu): Add trunc index.
|
idIndex *truncindex.TruncIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadStore loads images from runtime.
|
// LoadStore loads images from runtime.
|
||||||
@ -57,23 +58,36 @@ func LoadStore() *Store { return nil }
|
|||||||
|
|
||||||
// NewStore creates an image store.
|
// NewStore creates an image store.
|
||||||
func NewStore() *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.
|
// Add an image into the store.
|
||||||
func (s *Store) Add(img Image) {
|
func (s *Store) Add(img Image) error {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
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]
|
i, ok := s.images[img.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
// If the image doesn't exist, add it.
|
// If the image doesn't exist, add it.
|
||||||
s.images[img.ID] = img
|
s.images[img.ID] = img
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
// Or else, merge the repo tags/digests.
|
// Or else, merge the repo tags/digests.
|
||||||
i.RepoTags = mergeStringSlices(i.RepoTags, img.RepoTags)
|
i.RepoTags = mergeStringSlices(i.RepoTags, img.RepoTags)
|
||||||
i.RepoDigests = mergeStringSlices(i.RepoDigests, img.RepoDigests)
|
i.RepoDigests = mergeStringSlices(i.RepoDigests, img.RepoDigests)
|
||||||
s.images[img.ID] = i
|
s.images[img.ID] = i
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the image with specified id. Returns store.ErrNotExist if the
|
// Get returns the image with specified id. Returns store.ErrNotExist if the
|
||||||
@ -81,6 +95,13 @@ func (s *Store) Add(img Image) {
|
|||||||
func (s *Store) Get(id string) (Image, error) {
|
func (s *Store) Get(id string) (Image, error) {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
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 {
|
if i, ok := s.images[id]; ok {
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
@ -102,6 +123,13 @@ func (s *Store) List() []Image {
|
|||||||
func (s *Store) Delete(id string) {
|
func (s *Store) Delete(id string) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
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)
|
delete(s.images, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,22 +35,30 @@ func TestImageStore(t *testing.T) {
|
|||||||
Size: 10,
|
Size: 10,
|
||||||
Config: &imagespec.ImageConfig{},
|
Config: &imagespec.ImageConfig{},
|
||||||
},
|
},
|
||||||
"2": {
|
"2abcd": {
|
||||||
ID: "2",
|
ID: "2abcd",
|
||||||
ChainID: "test-chain-id-2",
|
ChainID: "test-chain-id-2abcd",
|
||||||
RepoTags: []string{"tag-2"},
|
RepoTags: []string{"tag-2abcd"},
|
||||||
RepoDigests: []string{"digest-2"},
|
RepoDigests: []string{"digest-2abcd"},
|
||||||
Size: 20,
|
Size: 20,
|
||||||
Config: &imagespec.ImageConfig{},
|
Config: &imagespec.ImageConfig{},
|
||||||
},
|
},
|
||||||
"3": {
|
"4a333": {
|
||||||
ID: "3",
|
ID: "4a333",
|
||||||
RepoTags: []string{"tag-3"},
|
RepoTags: []string{"tag-4a333"},
|
||||||
RepoDigests: []string{"digest-3"},
|
RepoDigests: []string{"digest-4a333"},
|
||||||
ChainID: "test-chain-id-3",
|
ChainID: "test-chain-id-4a333",
|
||||||
Size: 30,
|
Size: 30,
|
||||||
Config: &imagespec.ImageConfig{},
|
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)
|
assert := assertlib.New(t)
|
||||||
|
|
||||||
@ -58,49 +66,62 @@ func TestImageStore(t *testing.T) {
|
|||||||
|
|
||||||
t.Logf("should be able to add image")
|
t.Logf("should be able to add image")
|
||||||
for _, img := range images {
|
for _, img := range images {
|
||||||
s.Add(img)
|
err := s.Add(img)
|
||||||
|
assert.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("should be able to get image")
|
t.Logf("should be able to get image")
|
||||||
|
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||||
for id, img := range images {
|
for id, img := range images {
|
||||||
got, err := s.Get(id)
|
got, err := s.Get(genTruncIndex(id))
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(img, got)
|
assert.Equal(img, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("should be able to list images")
|
t.Logf("should be able to list images")
|
||||||
imgs := s.List()
|
imgs := s.List()
|
||||||
assert.Len(imgs, 3)
|
assert.Len(imgs, len(images))
|
||||||
|
|
||||||
testID := "2"
|
imageNum := len(images)
|
||||||
t.Logf("should be able to add new repo tags/digests")
|
for testID, v := range images {
|
||||||
newImg := images[testID]
|
truncID := genTruncIndex(testID)
|
||||||
newImg.RepoTags = []string{"tag-new"}
|
oldRepoTag := v.RepoTags[0]
|
||||||
newImg.RepoDigests = []string{"digest-new"}
|
oldRepoDigest := v.RepoDigests[0]
|
||||||
s.Add(newImg)
|
newRepoTag := oldRepoTag + "new"
|
||||||
got, err := s.Get(testID)
|
newRepoDigest := oldRepoDigest + "new"
|
||||||
assert.NoError(err)
|
|
||||||
assert.Len(got.RepoTags, 2)
|
|
||||||
assert.Contains(got.RepoTags, "tag-2", "tag-new")
|
|
||||||
assert.Len(got.RepoDigests, 2)
|
|
||||||
assert.Contains(got.RepoDigests, "digest-2", "digest-new")
|
|
||||||
|
|
||||||
t.Logf("should not be able to add duplicated repo tags/digests")
|
t.Logf("should be able to add new repo tags/digests")
|
||||||
s.Add(newImg)
|
newImg := v
|
||||||
got, err = s.Get(testID)
|
newImg.RepoTags = []string{newRepoTag}
|
||||||
assert.NoError(err)
|
newImg.RepoDigests = []string{newRepoDigest}
|
||||||
assert.Len(got.RepoTags, 2)
|
err := s.Add(newImg)
|
||||||
assert.Contains(got.RepoTags, "tag-2", "tag-new")
|
assert.NoError(err)
|
||||||
assert.Len(got.RepoDigests, 2)
|
got, err := s.Get(truncID)
|
||||||
assert.Contains(got.RepoDigests, "digest-2", "digest-new")
|
assert.NoError(err)
|
||||||
|
assert.Len(got.RepoTags, 2)
|
||||||
|
assert.Contains(got.RepoTags, oldRepoTag, newRepoTag)
|
||||||
|
assert.Len(got.RepoDigests, 2)
|
||||||
|
assert.Contains(got.RepoDigests, oldRepoDigest, newRepoDigest)
|
||||||
|
|
||||||
t.Logf("should be able to delete image")
|
t.Logf("should not be able to add duplicated repo tags/digests")
|
||||||
s.Delete(testID)
|
err = s.Add(newImg)
|
||||||
imgs = s.List()
|
assert.NoError(err)
|
||||||
assert.Len(imgs, 2)
|
got, err = s.Get(truncID)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Len(got.RepoTags, 2)
|
||||||
|
assert.Contains(got.RepoTags, oldRepoTag, newRepoTag)
|
||||||
|
assert.Len(got.RepoDigests, 2)
|
||||||
|
assert.Contains(got.RepoDigests, oldRepoDigest, newRepoDigest)
|
||||||
|
|
||||||
t.Logf("get should return empty struct and ErrNotExist after deletion")
|
t.Logf("should be able to delete image")
|
||||||
img, err := s.Get(testID)
|
s.Delete(truncID)
|
||||||
assert.Equal(Image{}, img)
|
imageNum--
|
||||||
assert.Equal(store.ErrNotExist, err)
|
imgs = s.List()
|
||||||
|
assert.Len(imgs, imageNum)
|
||||||
|
|
||||||
|
t.Logf("get should return empty struct and ErrNotExist after deletion")
|
||||||
|
img, err := s.Get(truncID)
|
||||||
|
assert.Equal(Image{}, img)
|
||||||
|
assert.Equal(store.ErrNotExist, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/docker/docker/pkg/truncindex"
|
||||||
|
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/store"
|
||||||
)
|
)
|
||||||
@ -39,7 +40,7 @@ type Sandbox struct {
|
|||||||
type Store struct {
|
type Store struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
sandboxes map[string]Sandbox
|
sandboxes map[string]Sandbox
|
||||||
// TODO(random-liu): Add trunc index.
|
idIndex *truncindex.TruncIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadStore loads sandboxes from runtime.
|
// LoadStore loads sandboxes from runtime.
|
||||||
@ -48,7 +49,10 @@ func LoadStore() *Store { return nil }
|
|||||||
|
|
||||||
// NewStore creates a sandbox store.
|
// NewStore creates a sandbox store.
|
||||||
func NewStore() *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.
|
// Add a sandbox into the store.
|
||||||
@ -58,6 +62,9 @@ func (s *Store) Add(sb Sandbox) error {
|
|||||||
if _, ok := s.sandboxes[sb.ID]; ok {
|
if _, ok := s.sandboxes[sb.ID]; ok {
|
||||||
return store.ErrAlreadyExist
|
return store.ErrAlreadyExist
|
||||||
}
|
}
|
||||||
|
if err := s.idIndex.Add(sb.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.sandboxes[sb.ID] = sb
|
s.sandboxes[sb.ID] = sb
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -67,6 +74,13 @@ func (s *Store) Add(sb Sandbox) error {
|
|||||||
func (s *Store) Get(id string) (Sandbox, error) {
|
func (s *Store) Get(id string) (Sandbox, error) {
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
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 {
|
if sb, ok := s.sandboxes[id]; ok {
|
||||||
return sb, nil
|
return sb, nil
|
||||||
}
|
}
|
||||||
@ -88,5 +102,12 @@ func (s *Store) List() []Sandbox {
|
|||||||
func (s *Store) Delete(id string) {
|
func (s *Store) Delete(id string) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
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)
|
delete(s.sandboxes, id)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSandboxStore(t *testing.T) {
|
func TestSandboxStore(t *testing.T) {
|
||||||
ids := []string{"1", "2", "3"}
|
|
||||||
metadatas := map[string]Metadata{
|
metadatas := map[string]Metadata{
|
||||||
"1": {
|
"1": {
|
||||||
ID: "1",
|
ID: "1",
|
||||||
@ -41,36 +40,49 @@ func TestSandboxStore(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-1",
|
NetNSPath: "TestNetNS-1",
|
||||||
},
|
},
|
||||||
"2": {
|
"2abcd": {
|
||||||
ID: "2",
|
ID: "2abcd",
|
||||||
Name: "Sandbox-2",
|
Name: "Sandbox-2abcd",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Name: "TestPod-2",
|
Name: "TestPod-2abcd",
|
||||||
Uid: "TestUid-2",
|
Uid: "TestUid-2abcd",
|
||||||
Namespace: "TestNamespace-2",
|
Namespace: "TestNamespace-2abcd",
|
||||||
Attempt: 2,
|
Attempt: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-2",
|
NetNSPath: "TestNetNS-2",
|
||||||
},
|
},
|
||||||
"3": {
|
"4a333": {
|
||||||
ID: "3",
|
ID: "4a333",
|
||||||
Name: "Sandbox-3",
|
Name: "Sandbox-4a333",
|
||||||
Config: &runtime.PodSandboxConfig{
|
Config: &runtime.PodSandboxConfig{
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
Name: "TestPod-3",
|
Name: "TestPod-4a333",
|
||||||
Uid: "TestUid-3",
|
Uid: "TestUid-4a333",
|
||||||
Namespace: "TestNamespace-3",
|
Namespace: "TestNamespace-4a333",
|
||||||
Attempt: 3,
|
Attempt: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NetNSPath: "TestNetNS-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)
|
assert := assertlib.New(t)
|
||||||
sandboxes := map[string]Sandbox{}
|
sandboxes := map[string]Sandbox{}
|
||||||
for _, id := range ids {
|
for id := range metadatas {
|
||||||
sandboxes[id] = Sandbox{Metadata: metadatas[id]}
|
sandboxes[id] = Sandbox{Metadata: metadatas[id]}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,27 +94,33 @@ func TestSandboxStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("should be able to get sandbox")
|
t.Logf("should be able to get sandbox")
|
||||||
|
genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] }
|
||||||
for id, sb := range sandboxes {
|
for id, sb := range sandboxes {
|
||||||
got, err := s.Get(id)
|
got, err := s.Get(genTruncIndex(id))
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(sb, got)
|
assert.Equal(sb, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("should be able to list sandboxes")
|
t.Logf("should be able to list sandboxes")
|
||||||
sbs := s.List()
|
sbs := s.List()
|
||||||
assert.Len(sbs, 3)
|
assert.Len(sbs, len(sandboxes))
|
||||||
|
|
||||||
testID := "2"
|
sbNum := len(sandboxes)
|
||||||
t.Logf("add should return already exists error for duplicated sandbox")
|
for testID, v := range sandboxes {
|
||||||
assert.Equal(store.ErrAlreadyExist, s.Add(sandboxes[testID]))
|
truncID := genTruncIndex(testID)
|
||||||
|
|
||||||
t.Logf("should be able to delete sandbox")
|
t.Logf("add should return already exists error for duplicated sandbox")
|
||||||
s.Delete(testID)
|
assert.Equal(store.ErrAlreadyExist, s.Add(v))
|
||||||
sbs = s.List()
|
|
||||||
assert.Len(sbs, 2)
|
|
||||||
|
|
||||||
t.Logf("get should return not exist error after deletion")
|
t.Logf("should be able to delete sandbox")
|
||||||
sb, err := s.Get(testID)
|
s.Delete(truncID)
|
||||||
assert.Equal(Sandbox{}, sb)
|
sbNum--
|
||||||
assert.Equal(store.ErrNotExist, err)
|
sbs = s.List()
|
||||||
|
assert.Len(sbs, sbNum)
|
||||||
|
|
||||||
|
t.Logf("get should return not exist error after deletion")
|
||||||
|
sb, err := s.Get(truncID)
|
||||||
|
assert.Equal(Sandbox{}, sb)
|
||||||
|
assert.Equal(store.ErrNotExist, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,3 +66,4 @@ k8s.io/client-go c6f8cf2c47d21d55fa0df928291b2580544886c8
|
|||||||
k8s.io/kube-openapi abfc5fbe1cf87ee697db107fdfd24c32fe4397a8
|
k8s.io/kube-openapi abfc5fbe1cf87ee697db107fdfd24c32fe4397a8
|
||||||
k8s.io/kubernetes 14b32888de6403aa38aedc69086c5a3aff7a4ace
|
k8s.io/kubernetes 14b32888de6403aa38aedc69086c5a3aff7a4ace
|
||||||
k8s.io/utils 4fe312863be2155a7b68acd2aff1c9221b24e68c
|
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