202 lines
4.7 KiB
Go
202 lines
4.7 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package feature
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/elastic/beats/libbeat/logp"
|
|
)
|
|
|
|
type mapper map[string]map[string]Featurable
|
|
|
|
// Registry implements a global FeatureRegistry for any kind of feature in beats.
|
|
// feature are grouped by namespace, a namespace is a kind of plugin like outputs, inputs, or queue.
|
|
// The feature name must be unique.
|
|
type Registry struct {
|
|
sync.RWMutex
|
|
namespaces mapper
|
|
log *logp.Logger
|
|
}
|
|
|
|
// NewRegistry returns a new registry.
|
|
func NewRegistry() *Registry {
|
|
return &Registry{
|
|
namespaces: make(mapper),
|
|
log: logp.NewLogger("registry"),
|
|
}
|
|
}
|
|
|
|
// Register registers a new feature into a specific namespace, namespace are lazy created.
|
|
// Feature name must be unique.
|
|
func (r *Registry) Register(feature Featurable) error {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
ns := normalize(feature.Namespace())
|
|
name := normalize(feature.Name())
|
|
|
|
if feature.Factory() == nil {
|
|
return fmt.Errorf("feature '%s' cannot be registered with a nil factory", name)
|
|
}
|
|
|
|
// Lazy create namespaces
|
|
_, found := r.namespaces[ns]
|
|
if !found {
|
|
r.namespaces[ns] = make(map[string]Featurable)
|
|
}
|
|
|
|
f, found := r.namespaces[ns][name]
|
|
if found {
|
|
if featuresEqual(feature, f) {
|
|
// Allow both old style and new style of plugin to work together.
|
|
r.log.Debugw(
|
|
"ignoring, feature '%s' is already registered in the namespace '%s'",
|
|
name,
|
|
ns,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf(
|
|
"could not register new feature '%s' in namespace '%s', feature name must be unique",
|
|
name,
|
|
ns,
|
|
)
|
|
}
|
|
|
|
r.log.Debugw(
|
|
"registering new feature",
|
|
"namespace",
|
|
ns,
|
|
"name",
|
|
name,
|
|
)
|
|
|
|
r.namespaces[ns][name] = feature
|
|
|
|
return nil
|
|
}
|
|
|
|
// Unregister removes a feature from the registry.
|
|
func (r *Registry) Unregister(namespace, name string) error {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
ns := normalize(namespace)
|
|
|
|
v, found := r.namespaces[ns]
|
|
if !found {
|
|
return fmt.Errorf("unknown namespace named '%s'", ns)
|
|
}
|
|
|
|
_, found = v[name]
|
|
if !found {
|
|
return fmt.Errorf("unknown feature '%s' in namespace '%s'", name, ns)
|
|
}
|
|
|
|
delete(r.namespaces[ns], name)
|
|
return nil
|
|
}
|
|
|
|
// Lookup searches for a Feature by the namespace-name pair.
|
|
func (r *Registry) Lookup(namespace, name string) (Featurable, error) {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
|
|
ns := normalize(namespace)
|
|
n := normalize(name)
|
|
|
|
v, found := r.namespaces[ns]
|
|
if !found {
|
|
return nil, fmt.Errorf("unknown namespace named '%s'", ns)
|
|
}
|
|
|
|
m, found := v[n]
|
|
if !found {
|
|
return nil, fmt.Errorf("unknown feature '%s' in namespace '%s'", n, ns)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// LookupAll returns all the features for a specific namespace.
|
|
func (r *Registry) LookupAll(namespace string) ([]Featurable, error) {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
|
|
ns := normalize(namespace)
|
|
|
|
v, found := r.namespaces[ns]
|
|
if !found {
|
|
return nil, fmt.Errorf("unknown namespace named '%s'", ns)
|
|
}
|
|
|
|
list := make([]Featurable, len(v))
|
|
c := 0
|
|
for _, feature := range v {
|
|
list[c] = feature
|
|
c++
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
// Overwrite allow to replace an existing feature with a new implementation.
|
|
func (r *Registry) Overwrite(feature Featurable) error {
|
|
_, err := r.Lookup(feature.Namespace(), feature.Name())
|
|
if err == nil {
|
|
err := r.Unregister(feature.Namespace(), feature.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return r.Register(feature)
|
|
}
|
|
|
|
// Size returns the number of registered features in the registry.
|
|
func (r *Registry) Size() int {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
|
|
c := 0
|
|
for _, namespace := range r.namespaces {
|
|
c += len(namespace)
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
func featuresEqual(f1, f2 Featurable) bool {
|
|
// There is no safe way to compare function in go,
|
|
// but since the function pointers are global it should be stable.
|
|
if f1.Name() == f2.Name() &&
|
|
f1.Namespace() == f2.Namespace() &&
|
|
reflect.ValueOf(f1.Factory()).Pointer() == reflect.ValueOf(f2.Factory()).Pointer() {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func normalize(s string) string {
|
|
return strings.ToLower(s)
|
|
}
|