summaryrefslogtreecommitdiff
path: root/pkg/registrar
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/registrar')
-rw-r--r--pkg/registrar/registrar.go127
-rw-r--r--pkg/registrar/registrar_test.go119
2 files changed, 246 insertions, 0 deletions
diff --git a/pkg/registrar/registrar.go b/pkg/registrar/registrar.go
new file mode 100644
index 000000000..1e75ee995
--- /dev/null
+++ b/pkg/registrar/registrar.go
@@ -0,0 +1,127 @@
+// Package registrar provides name registration. It reserves a name to a given key.
+package registrar
+
+import (
+ "errors"
+ "sync"
+)
+
+var (
+ // ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved
+ ErrNameReserved = errors.New("name is reserved")
+ // ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved
+ ErrNameNotReserved = errors.New("name is not reserved")
+ // ErrNoSuchKey is returned when trying to find the names for a key which is not known
+ ErrNoSuchKey = errors.New("provided key does not exist")
+)
+
+// Registrar stores indexes a list of keys and their registered names as well as indexes names and the key that they are registered to
+// Names must be unique.
+// Registrar is safe for concurrent access.
+type Registrar struct {
+ idx map[string][]string
+ names map[string]string
+ mu sync.Mutex
+}
+
+// NewRegistrar creates a new Registrar with the an empty index
+func NewRegistrar() *Registrar {
+ return &Registrar{
+ idx: make(map[string][]string),
+ names: make(map[string]string),
+ }
+}
+
+// Reserve registers a key to a name
+// Reserve is idempotent
+// Attempting to reserve a key to a name that already exists results in an `ErrNameReserved`
+// A name reservation is globally unique
+func (r *Registrar) Reserve(name, key string) error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if k, exists := r.names[name]; exists {
+ if k != key {
+ return ErrNameReserved
+ }
+ return nil
+ }
+
+ r.idx[key] = append(r.idx[key], name)
+ r.names[name] = key
+ return nil
+}
+
+// Release releases the reserved name
+// Once released, a name can be reserved again
+func (r *Registrar) Release(name string) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ key, exists := r.names[name]
+ if !exists {
+ return
+ }
+
+ for i, n := range r.idx[key] {
+ if n != name {
+ continue
+ }
+ r.idx[key] = append(r.idx[key][:i], r.idx[key][i+1:]...)
+ break
+ }
+
+ delete(r.names, name)
+
+ if len(r.idx[key]) == 0 {
+ delete(r.idx, key)
+ }
+}
+
+// Delete removes all reservations for the passed in key.
+// All names reserved to this key are released.
+func (r *Registrar) Delete(key string) {
+ r.mu.Lock()
+ for _, name := range r.idx[key] {
+ delete(r.names, name)
+ }
+ delete(r.idx, key)
+ r.mu.Unlock()
+}
+
+// GetNames lists all the reserved names for the given key
+func (r *Registrar) GetNames(key string) ([]string, error) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ names, exists := r.idx[key]
+ if !exists {
+ return nil, ErrNoSuchKey
+ }
+ return names, nil
+}
+
+// Get returns the key that the passed in name is reserved to
+func (r *Registrar) Get(name string) (string, error) {
+ r.mu.Lock()
+ key, exists := r.names[name]
+ r.mu.Unlock()
+
+ if !exists {
+ return "", ErrNameNotReserved
+ }
+ return key, nil
+}
+
+// GetAll returns all registered names
+func (r *Registrar) GetAll() map[string][]string {
+ out := make(map[string][]string)
+
+ r.mu.Lock()
+ // copy index into out
+ for id, names := range r.idx {
+ out[id] = names
+ }
+ r.mu.Unlock()
+ return out
+}
diff --git a/pkg/registrar/registrar_test.go b/pkg/registrar/registrar_test.go
new file mode 100644
index 000000000..0c1ef312a
--- /dev/null
+++ b/pkg/registrar/registrar_test.go
@@ -0,0 +1,119 @@
+package registrar
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestReserve(t *testing.T) {
+ r := NewRegistrar()
+
+ obj := "test1"
+ if err := r.Reserve("test", obj); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := r.Reserve("test", obj); err != nil {
+ t.Fatal(err)
+ }
+
+ obj2 := "test2"
+ err := r.Reserve("test", obj2)
+ if err == nil {
+ t.Fatalf("expected error when reserving an already reserved name to another object")
+ }
+ if err != ErrNameReserved {
+ t.Fatal("expected `ErrNameReserved` error when attempting to reserve an already reserved name")
+ }
+}
+
+func TestRelease(t *testing.T) {
+ r := NewRegistrar()
+ obj := "testing"
+
+ if err := r.Reserve("test", obj); err != nil {
+ t.Fatal(err)
+ }
+ r.Release("test")
+ r.Release("test") // Ensure there is no panic here
+
+ if err := r.Reserve("test", obj); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestGetNames(t *testing.T) {
+ r := NewRegistrar()
+ obj := "testing"
+ names := []string{"test1", "test2"}
+
+ for _, name := range names {
+ if err := r.Reserve(name, obj); err != nil {
+ t.Fatal(err)
+ }
+ }
+ r.Reserve("test3", "other")
+
+ names2, err := r.GetNames(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(names, names2) {
+ t.Fatalf("Exepected: %v, Got: %v", names, names2)
+ }
+}
+
+func TestDelete(t *testing.T) {
+ r := NewRegistrar()
+ obj := "testing"
+ names := []string{"test1", "test2"}
+ for _, name := range names {
+ if err := r.Reserve(name, obj); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ r.Reserve("test3", "other")
+ r.Delete(obj)
+
+ _, err := r.GetNames(obj)
+ if err == nil {
+ t.Fatal("expected error getting names for deleted key")
+ }
+
+ if err != ErrNoSuchKey {
+ t.Fatal("expected `ErrNoSuchKey`")
+ }
+}
+
+func TestGet(t *testing.T) {
+ r := NewRegistrar()
+ obj := "testing"
+ name := "test"
+
+ _, err := r.Get(name)
+ if err == nil {
+ t.Fatal("expected error when key does not exist")
+ }
+ if err != ErrNameNotReserved {
+ t.Fatal(err)
+ }
+
+ if err := r.Reserve(name, obj); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = r.Get(name); err != nil {
+ t.Fatal(err)
+ }
+
+ r.Delete(obj)
+ _, err = r.Get(name)
+ if err == nil {
+ t.Fatal("expected error when key does not exist")
+ }
+ if err != ErrNameNotReserved {
+ t.Fatal(err)
+ }
+}