... | @@ -53,13 +53,13 @@ The plugin object provides a single `Lookup` function. It accepts a string ident |
... | @@ -53,13 +53,13 @@ The plugin object provides a single `Lookup` function. It accepts a string ident |
|
|
|
|
|
It is possible to implement the Kea hook structure in Go. We can search for `load`, `unload`, and `version` functions (`multi_threading_compatible` is not necessary) or any callout. Callouts can have the same signature as in Kea.
|
|
It is possible to implement the Kea hook structure in Go. We can search for `load`, `unload`, and `version` functions (`multi_threading_compatible` is not necessary) or any callout. Callouts can have the same signature as in Kea.
|
|
|
|
|
|
I think that we can slightly modify the primary functions set. Stork should search for plugins in a specific directory (it should be the same for all plugins) and load them on startup by calling the mandatory `Load` function. This function's responsibility will be to create an object with callout points. This object may optionally implement the `Close` method (as in `io.Closer` interface) that will be called during gracefully stopping the server. The plugin needs to provide the `version` function too. It will return the program name (one for server and another for agent) and version string that will be used to check the compatibility between a core and a plugin.
|
|
I think that we can slightly modify the primary functions set. Stork should search for plugins in a specific directory (it should be the same for all plugins) and load them on startup by calling the mandatory `Load` function. This function's responsibility will be to create an object with callout points. This object must mandatorily implement the `Close` method (as in `io.Closer` interface) that will be called during gracefully stopping the server. The plugin needs to provide the `version` function too. It will return the program name (one for server and another for agent) and version string that will be used to check the compatibility between a core and a plugin.
|
|
|
|
|
|
However, we should redesign the callout signature. It should be strongly typed but not strongly related to the core codebase. The function shouldn't accept the `CalloutHandle` object and lookup in runtime for specific arguments. It should just take all needed arguments. The expected signature can be defined as an interface in the hook module and used with an interface check in the hook implementation.
|
|
However, we should redesign the callout signature. It should be strongly typed but not strongly related to the core codebase. The function shouldn't accept the `CalloutHandle` object and lookup in runtime for specific arguments. It should just take all needed arguments. The expected signature can be defined as an interface in the hook module and used with an interface check in the hook implementation.
|
|
|
|
|
|
In the Kea layout, the hook is a set of callout functions. It means that we need to search each callout by name. We must share between the core and hook the type (function signature) and the identifier (function name). In Go, the hook can provide an object that will be tested for implementing expected hook interfaces. We don't need to use any identifiers.
|
|
In the Kea layout, the hook is a set of callout functions. It means that we need to search each callout by name. We must share between the core and hook the type (function signature) and the identifier (function name). In Go, the hook can provide an object that will be tested for implementing expected hook interfaces. We don't need to use any identifiers.
|
|
|
|
|
|
Opened question is how to exchange data between hooks registered on the same callout. I think that the specific solution depends on the specific hook.
|
|
Opened question is how to exchange data between hooks registered on the same callout. This problem is discussed in the next section. The specific solution needs to be chosen based on the hook characteristics.
|
|
|
|
|
|
We probably need to implement something similar to the `LibraryManager` from Kea to manage the hooks. We also need the `HooksManager` to register and call the hook callouts.
|
|
We probably need to implement something similar to the `LibraryManager` from Kea to manage the hooks. We also need the `HooksManager` to register and call the hook callouts.
|
|
|
|
|
... | @@ -215,7 +215,7 @@ The `SetupSettings` function accepts settings registry [`SettingsRegistry`]. Thi |
... | @@ -215,7 +215,7 @@ The `SetupSettings` function accepts settings registry [`SettingsRegistry`]. Thi |
|
|
|
|
|
The `Load` function accepts the observer of settings [`SettingsObserver`]. It may be used to register on settings notifications. The given function will be called on change of the specific setting.
|
|
The `Load` function accepts the observer of settings [`SettingsObserver`]. It may be used to register on settings notifications. The given function will be called on change of the specific setting.
|
|
|
|
|
|
The hook creator must pick one or more callout interfaces and implement them in a single structure [`FooImpl`/`BarBazImpl`]. The structure may provide the `Close` function. It will be called during a graceful shutdown. The structure instance must be created by the top-level `Load` function. The creator also must implement the top-level `Version` function using the current version [`CurrentVersion'] constant provided by the hook module.
|
|
The hook creator must pick one or more callout interfaces and implement them in a single structure [`FooImpl`/`BarBazImpl`]. The structure must provide the `Close` function. It will be called during a graceful shutdown. The structure instance must be created by the top-level `Load` function. The creator also must implement the top-level `Version` function using the current version [`CurrentVersion'] constant provided by the hook module.
|
|
|
|
|
|
The core needs to provide the hook manager [`AgentHookManager`/`ServerHookManager`] as a facade for the hooks. It is responsible for calling the callout points. The hook manager implements all supported callout points and defers calls to the executor [`HookExecutor`]. Executor contains all callout implementations from hooks and provides methods for calling them. The callouts can be called sequentially "one-by-one," to first success, to first fail, etc. The calling order is defined by the hook manager depending on the characteristics of the callout point. The construction of the executor has two phases. First, the hook manager creates an instance and registers all support callouts' types (interfaces). Next, the hook loader [`HookLoader`] retrieves the callout point objects and registers them in the executor. Now, the executor contains the mapping between a callout type (interface) and its implementations.
|
|
The core needs to provide the hook manager [`AgentHookManager`/`ServerHookManager`] as a facade for the hooks. It is responsible for calling the callout points. The hook manager implements all supported callout points and defers calls to the executor [`HookExecutor`]. Executor contains all callout implementations from hooks and provides methods for calling them. The callouts can be called sequentially "one-by-one," to first success, to first fail, etc. The calling order is defined by the hook manager depending on the characteristics of the callout point. The construction of the executor has two phases. First, the hook manager creates an instance and registers all support callouts' types (interfaces). Next, the hook loader [`HookLoader`] retrieves the callout point objects and registers them in the executor. Now, the executor contains the mapping between a callout type (interface) and its implementations.
|
|
|
|
|
... | @@ -384,23 +384,32 @@ Supported operations: |
... | @@ -384,23 +384,32 @@ Supported operations: |
|
|
|
|
|
```go
|
|
```go
|
|
func Load() (interface{}, error) {
|
|
func Load() (interface{}, error) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
7. Prepare top-level close function
|
|
|
|
|
|
|
|
```go
|
|
|
|
func Close() error {
|
|
return &Callouts{}, nil
|
|
return &Callouts{}, nil
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
|
|
7. Compile to a plugin file
|
|
8. Compile to a plugin file
|
|
|
|
|
|
```go
|
|
```go
|
|
To be done...
|
|
To be done...
|
|
go build --plugin ...
|
|
go build --plugin ...
|
|
```
|
|
```
|
|
|
|
|
|
8. Copy the plugin file to the hook directory
|
|
9. Copy the plugin file to the hook directory
|
|
|
|
|
|
```sh
|
|
```sh
|
|
cp foo-hook.go /var/lib/stork-server/hooks
|
|
cp foo-hook.go /var/lib/stork-server/hooks
|
|
```
|
|
```
|
|
|
|
|
|
9. Run the Stork. Enjoy!
|
|
10. Run the Stork. Enjoy!
|
|
|
|
|
|
## Implementation examples
|
|
## Implementation examples
|
|
|
|
|
... | @@ -457,7 +466,7 @@ func (hm *HookManager) OnBeforeForwardToKeaOverHTTP(in *agentapi.ForwardToKeaOve |
... | @@ -457,7 +466,7 @@ func (hm *HookManager) OnBeforeForwardToKeaOverHTTP(in *agentapi.ForwardToKeaOve |
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
The hook manager should implement the `Close` function to clean up all hooks. It just calls the unregister function of the executor.
|
|
The hook manager must implement the `Close` function to clean up all hooks. It just calls the unregister function of the executor.
|
|
|
|
|
|
```go
|
|
```go
|
|
func (hm *HookManager) Close() {
|
|
func (hm *HookManager) Close() {
|
... | | ... | |