mirror of https://github.com/hashicorp/consul
Basic resource type registry (#16622)
parent
8f75d99299
commit
5a3fec6238
@ -0,0 +1,71 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry interface {
|
||||||
|
// Register the given resource type and its hooks.
|
||||||
|
Register(reg Registration)
|
||||||
|
|
||||||
|
// Resolve the given resource type and its hooks.
|
||||||
|
Resolve(typ *pbresource.Type) (reg Registration, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registration struct {
|
||||||
|
// Type is the GVK of the resource type.
|
||||||
|
Type *pbresource.Type
|
||||||
|
|
||||||
|
// In the future, we'll add hooks, the controller etc. here.
|
||||||
|
// TODO: https://github.com/hashicorp/consul/pull/16622#discussion_r1134515909
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashable key for a resource type
|
||||||
|
type TypeKey string
|
||||||
|
|
||||||
|
// Resource type registry
|
||||||
|
type TypeRegistry struct {
|
||||||
|
// registrations keyed by GVK
|
||||||
|
registrations map[string]Registration
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() Registry {
|
||||||
|
return &TypeRegistry{
|
||||||
|
registrations: make(map[string]Registration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry) Register(registration Registration) {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
typ := registration.Type
|
||||||
|
if typ.Group == "" || typ.GroupVersion == "" || typ.Kind == "" {
|
||||||
|
panic("type field(s) cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ToGVK(registration.Type)
|
||||||
|
if _, ok := r.registrations[key]; ok {
|
||||||
|
panic(fmt.Sprintf("resource type %s already registered", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.registrations[key] = registration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry) Resolve(typ *pbresource.Type) (reg Registration, ok bool) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
if registration, ok := r.registrations[ToGVK(typ)]; ok {
|
||||||
|
return registration, true
|
||||||
|
}
|
||||||
|
return Registration{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToGVK(resourceType *pbresource.Type) string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s", resourceType.Group, resourceType.GroupVersion, resourceType.Kind)
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
r := NewRegistry()
|
||||||
|
|
||||||
|
serviceType := &pbresource.Type{
|
||||||
|
Group: "mesh",
|
||||||
|
GroupVersion: "v1",
|
||||||
|
Kind: "service",
|
||||||
|
}
|
||||||
|
|
||||||
|
// register
|
||||||
|
serviceRegistration := Registration{Type: serviceType}
|
||||||
|
r.Register(serviceRegistration)
|
||||||
|
|
||||||
|
// register existing should panic
|
||||||
|
assertRegisterPanics(t, r.Register, serviceRegistration, "resource type mesh/v1/service already registered")
|
||||||
|
|
||||||
|
// register empty Group should panic
|
||||||
|
assertRegisterPanics(t, r.Register, Registration{
|
||||||
|
Type: &pbresource.Type{
|
||||||
|
Group: "",
|
||||||
|
GroupVersion: "v1",
|
||||||
|
Kind: "service",
|
||||||
|
},
|
||||||
|
}, "type field(s) cannot be empty")
|
||||||
|
|
||||||
|
// register empty GroupVersion should panic
|
||||||
|
assertRegisterPanics(t, r.Register, Registration{
|
||||||
|
Type: &pbresource.Type{
|
||||||
|
Group: "mesh",
|
||||||
|
GroupVersion: "",
|
||||||
|
Kind: "service",
|
||||||
|
},
|
||||||
|
}, "type field(s) cannot be empty")
|
||||||
|
|
||||||
|
// register empty Kind should panic
|
||||||
|
assertRegisterPanics(t, r.Register, Registration{
|
||||||
|
Type: &pbresource.Type{
|
||||||
|
Group: "mesh",
|
||||||
|
GroupVersion: "v1",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
|
}, "type field(s) cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertRegisterPanics(t *testing.T, registerFn func(reg Registration), registration Registration, panicString string) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Errorf("expected panic, but none occurred")
|
||||||
|
} else {
|
||||||
|
errstr, ok := r.(string)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("unexpected error type returned from panic")
|
||||||
|
} else if errstr != panicString {
|
||||||
|
t.Errorf("expected %s error message but got: %s", panicString, errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
registerFn(registration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolve(t *testing.T) {
|
||||||
|
r := NewRegistry()
|
||||||
|
|
||||||
|
serviceType := &pbresource.Type{
|
||||||
|
Group: "mesh",
|
||||||
|
GroupVersion: "v1",
|
||||||
|
Kind: "service",
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
_, ok := r.Resolve(serviceType)
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
// found
|
||||||
|
r.Register(Registration{Type: serviceType})
|
||||||
|
registration, ok := r.Resolve(serviceType)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, registration.Type, serviceType)
|
||||||
|
}
|
Loading…
Reference in new issue