-
Notifications
You must be signed in to change notification settings - Fork 110
/
registry.go
325 lines (282 loc) · 11.5 KB
/
registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// Package registry operates the global registry of robotic parts.
package registry
import (
"context"
"fmt"
"runtime"
"strings"
"sync"
"github.com/edaniels/golog"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/grpcreflect"
"github.com/mitchellh/copystructure"
"github.com/pkg/errors"
"go.viam.com/utils"
"go.viam.com/utils/rpc"
"google.golang.org/grpc"
"go.viam.com/rdk/config"
"go.viam.com/rdk/discovery"
"go.viam.com/rdk/resource"
"go.viam.com/rdk/robot"
"go.viam.com/rdk/subtype"
)
type (
// A CreateServiceWithRobot creates a resource from a robot and a given config.
CreateServiceWithRobot func(ctx context.Context, r robot.Robot, config config.Service, logger golog.Logger) (interface{}, error)
// A CreateService creates a resource from a collection of dependencies and a given config.
CreateService func(ctx context.Context, deps Dependencies, config config.Service, logger golog.Logger) (interface{}, error)
)
// RegDebugInfo represents some runtime information about the registration used
// for debugging purposes.
type RegDebugInfo struct {
RegistrarLoc string
}
// Service stores a Service constructor (mandatory) and an attribute converter.
type Service struct {
RegDebugInfo
Constructor CreateService
AttributeMapConverter config.AttributeMapConverter
// This is a legacy constructor for default services
RobotConstructor CreateServiceWithRobot
}
func getCallerName() string {
pc, _, _, ok := runtime.Caller(2)
details := runtime.FuncForPC(pc)
if ok && details != nil {
return details.Name()
}
return "unknown"
}
// RegisterService registers a service type to a registration.
func RegisterService(subtype resource.Subtype, model resource.Model, creator Service) {
registryMu.Lock()
defer registryMu.Unlock()
creator.RegistrarLoc = getCallerName()
qName := fmt.Sprintf("%s/%s", subtype, model)
_, old := serviceRegistry[qName]
if old {
panic(errors.Errorf("trying to register two services with same subtype:%s, model:%s", subtype, model))
}
if creator.Constructor == nil && creator.RobotConstructor == nil {
panic(errors.Errorf("cannot register a nil constructor for subtype: %s", subtype))
}
serviceRegistry[qName] = creator
}
// DeregisterService removes a previously registered service.
func DeregisterService(subtype resource.Subtype, model resource.Model) {
registryMu.Lock()
defer registryMu.Unlock()
qName := fmt.Sprintf("%s/%s", subtype, model)
delete(serviceRegistry, qName)
}
// ServiceLookup looks up a service registration by the given type. nil is returned if
// there is no registration.
func ServiceLookup(subtype resource.Subtype, model resource.Model) *Service {
registryMu.RLock()
defer registryMu.RUnlock()
qName := fmt.Sprintf("%s/%s", subtype, model)
if registration, ok := RegisteredServices()[qName]; ok {
return ®istration
}
return nil
}
type (
// Dependencies is a map of resources that a component requires for creation.
Dependencies map[resource.Name]interface{}
// A CreateComponentWithRobot creates a resource from a robot and a given config.
CreateComponentWithRobot func(ctx context.Context, r robot.Robot, config config.Component, logger golog.Logger) (interface{}, error)
// A CreateComponent creates a resource from a collection of dependencies and a given config.
CreateComponent func(ctx context.Context, deps Dependencies, config config.Component, logger golog.Logger) (interface{}, error)
// A CreateReconfigurable makes a reconfigurable resource from a given resource.
CreateReconfigurable func(resource interface{}, name resource.Name) (resource.Reconfigurable, error)
// CreateStatus creates a status from a given resource. The return type is expected to be comprised of string keys
// (or it should be possible to decompose it into string keys) and values comprised of primitives, list of primitives,
// maps with string keys (or at least can be decomposed into one), or lists of the aforementioned type of maps.
// Results with other types of data are not guaranteed.
CreateStatus func(ctx context.Context, resource interface{}) (interface{}, error)
// A RegisterSubtypeRPCService will register the subtype service to the grpc server.
RegisterSubtypeRPCService func(ctx context.Context, rpcServer rpc.Server, subtypeSvc subtype.Service) error
// A CreateRPCClient will create the client for the resource.
CreateRPCClient func(ctx context.Context, conn rpc.ClientConn, name string, logger golog.Logger) interface{}
)
// A DependencyNotReadyError is used whenever we reference a dependency that has not been
// constructed and registered yet.
type DependencyNotReadyError struct {
Name string
}
func (e *DependencyNotReadyError) Error() string {
return fmt.Sprintf("dependency %q has not been registered yet", e.Name)
}
// Component stores a resource constructor (mandatory) and a Frame building function (optional).
type Component struct {
RegDebugInfo
Constructor CreateComponent
// TODO(RSDK-418): remove this legacy constructor once all components that use it no longer need to receive the entire robot.
RobotConstructor CreateComponentWithRobot
}
// ResourceSubtype stores subtype-specific functions and clients.
type ResourceSubtype struct {
Reconfigurable CreateReconfigurable
Status CreateStatus
RegisterRPCService RegisterSubtypeRPCService
RPCServiceDesc *grpc.ServiceDesc
ReflectRPCServiceDesc *desc.ServiceDescriptor `copy:"shallow"`
RPCClient CreateRPCClient
// MaxInstance sets a limit on the number of this subtype allowed on a robot.
// If MaxInstance is not set then it will default to 0 and there will be no limit.
MaxInstance int
}
// SubtypeGrpc stores functions necessary for a resource subtype to be accessible through grpc.
type SubtypeGrpc struct{}
// all registries.
var (
registryMu sync.RWMutex
componentRegistry = map[string]Component{}
subtypeRegistry = map[resource.Subtype]ResourceSubtype{}
serviceRegistry = map[string]Service{}
)
// RegisterComponent register a creator to its corresponding component and model.
func RegisterComponent(subtype resource.Subtype, model resource.Model, creator Component) {
registryMu.Lock()
defer registryMu.Unlock()
creator.RegistrarLoc = getCallerName()
qName := fmt.Sprintf("%s/%s", subtype.String(), model.String())
_, old := componentRegistry[qName]
if old {
panic(errors.Errorf("trying to register two resources with same subtype:%s, model:%s", subtype, model))
}
if creator.Constructor == nil && creator.RobotConstructor == nil {
panic(errors.Errorf("cannot register a nil constructor for subtype:%s, model:%s", subtype, model))
}
componentRegistry[qName] = creator
}
// DeregisterComponent removes a previously registered component.
func DeregisterComponent(subtype resource.Subtype, model resource.Model) {
registryMu.Lock()
defer registryMu.Unlock()
qName := fmt.Sprintf("%s/%s", subtype, model)
delete(componentRegistry, qName)
}
// ComponentLookup looks up a creator by the given subtype and model. nil is returned if
// there is no creator registered.
func ComponentLookup(subtype resource.Subtype, model resource.Model) *Component {
qName := fmt.Sprintf("%s/%s", subtype, model)
if registration, ok := RegisteredComponents()[qName]; ok {
return ®istration
}
return nil
}
// RegisterResourceSubtype register a ResourceSubtype to its corresponding component subtype.
func RegisterResourceSubtype(subtype resource.Subtype, creator ResourceSubtype) {
registryMu.Lock()
defer registryMu.Unlock()
_, old := subtypeRegistry[subtype]
if old {
panic(errors.Errorf("trying to register two of the same resource subtype: %s", subtype))
}
if creator.Reconfigurable == nil && creator.Status == nil &&
creator.RegisterRPCService == nil && creator.RPCClient == nil &&
creator.ReflectRPCServiceDesc == nil {
panic(errors.Errorf("cannot register a nil constructor for subtype: %s", subtype))
}
if creator.RegisterRPCService != nil && creator.RPCServiceDesc == nil {
panic(errors.Errorf("cannot register a RPC enabled subtype with no RPC service description: %s", subtype))
}
if creator.RPCServiceDesc != nil && creator.ReflectRPCServiceDesc == nil {
reflectSvcDesc, err := grpcreflect.LoadServiceDescriptor(creator.RPCServiceDesc)
if err != nil {
panic(err)
}
creator.ReflectRPCServiceDesc = reflectSvcDesc
}
subtypeRegistry[subtype] = creator
}
// DeregisterResourceSubtype removes a previously registered subtype.
func DeregisterResourceSubtype(subtype resource.Subtype) {
registryMu.Lock()
defer registryMu.Unlock()
delete(subtypeRegistry, subtype)
}
// ResourceSubtypeLookup looks up a ResourceSubtype by the given subtype. nil is returned if
// there is None.
func ResourceSubtypeLookup(subtype resource.Subtype) *ResourceSubtype {
if registration, ok := RegisteredResourceSubtypes()[subtype]; ok {
return ®istration
}
return nil
}
// RegisteredServices returns a copy of the registered services.
func RegisteredServices() map[string]Service {
registryMu.RLock()
defer registryMu.RUnlock()
copied, err := copystructure.Copy(serviceRegistry)
if err != nil {
panic(err)
}
return copied.(map[string]Service)
}
// RegisteredComponents returns a copy of the registered components.
func RegisteredComponents() map[string]Component {
registryMu.RLock()
defer registryMu.RUnlock()
copied, err := copystructure.Copy(componentRegistry)
if err != nil {
panic(err)
}
return copied.(map[string]Component)
}
// RegisteredResourceSubtypes returns a copy of the registered resource subtypes.
func RegisteredResourceSubtypes() map[resource.Subtype]ResourceSubtype {
registryMu.RLock()
defer registryMu.RUnlock()
toCopy := make(map[resource.Subtype]ResourceSubtype, len(subtypeRegistry))
for k, v := range subtypeRegistry {
toCopy[k] = v
}
return toCopy
}
var discoveryFunctions = map[discovery.Query]discovery.Discover{}
// DiscoveryFunctionLookup finds a discovery function registration for a given query.
func DiscoveryFunctionLookup(q discovery.Query) (discovery.Discover, bool) {
registryMu.RLock()
defer registryMu.RUnlock()
df, ok := discoveryFunctions[q]
return df, ok
}
// RegisterDiscoveryFunction registers a discovery function for a given query.
func RegisterDiscoveryFunction(q discovery.Query, discover discovery.Discover) {
_, ok := RegisteredResourceSubtypes()[q.API]
if !ok {
panic(errors.Errorf("trying to register discovery function for unregistered subtype %q", q.API))
}
if _, ok := discoveryFunctions[q]; ok {
panic(errors.Errorf("trying to register two discovery functions for subtype %q and model %q", q.API, q.Model))
}
discoveryFunctions[q] = discover
}
// FindValidServiceModels returns a list of valid models for a specified service.
func FindValidServiceModels(rName resource.Name) []resource.Model {
validModels := make([]resource.Model, 0)
for key := range RegisteredServices() {
if strings.Contains(key, rName.Subtype.String()) {
splitName := strings.Split(key, "/")
model, err := resource.NewModelFromString(splitName[1])
if err != nil {
utils.UncheckedError(err)
continue
}
validModels = append(validModels, model)
}
}
return validModels
}
// ReconfigurableComponent is implemented when component/service of a robot is reconfigurable.
type ReconfigurableComponent interface {
// Reconfigure reconfigures the resource
Reconfigure(ctx context.Context, cfg config.Component, deps Dependencies) error
}
// ReconfigurableService is implemented when component/service of a robot is reconfigurable.
type ReconfigurableService interface {
// Reconfigure reconfigures the resource
Reconfigure(ctx context.Context, cfg config.Service, deps Dependencies) error
}