/
plugin.go
190 lines (162 loc) · 6.51 KB
/
plugin.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
/*
Copyright 2020 The Magma Authors.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
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 plugin
import (
"fmt"
"io/ioutil"
"os"
"plugin"
"reflect"
"strings"
"github.com/go-magma/magma/lib/go/registry"
"github.com/go-magma/magma/lib/go/service/config"
"github.com/go-magma/magma/orc8r/cloud/go/obsidian"
"github.com/go-magma/magma/orc8r/cloud/go/orc8r"
"github.com/go-magma/magma/orc8r/cloud/go/serde"
"github.com/go-magma/magma/orc8r/cloud/go/services/configurator"
"github.com/go-magma/magma/orc8r/cloud/go/services/metricsd"
"github.com/go-magma/magma/orc8r/cloud/go/services/state/indexer"
"github.com/go-magma/magma/orc8r/cloud/go/services/streamer/providers"
"github.com/golang/glog"
)
const (
modulePluginDir = "/var/opt/magma/plugins/"
moduleFactoryFunction = "GetOrchestratorPlugin"
)
// OrchestratorPlugin defines the functionality that a plugin on the magma
// cloud side is expected to implement and provide. This interface is the
// formal surface area for integrating into and extending the magma
// orchestrator.
type OrchestratorPlugin interface {
// GetName returns a unique name for the plugin.
GetName() string
// GetServices returns a list of services that this plugin runs to register
// with the orc8r service registry.
GetServices() []registry.ServiceLocation
// GetSerdes returns a list of Serde implementations to register with the
// global serde registry. These serdes are the primary integration surface
// for many core orchestrator services.
GetSerdes() []serde.Serde
// GetMconfigBuilders returns a list of MconfigBuilders to register with
// the configurator service. These builder are responsible for constructing
// mconfigs to pass down to gateways.
GetMconfigBuilders() []configurator.MconfigBuilder
// GetMetricsProfiles returns the metricsd profiles that this module
// supplies. This will make specific configurations available for metricsd
// to load on startup. See MetricsProfile for additional documentation.
GetMetricsProfiles(metricsConfig *config.ConfigMap) []metricsd.MetricsProfile
// GetObsidianHandlers returns all the custom obsidian handlers for the
// plugin to add functionality to the REST API.
GetObsidianHandlers(metricsConfig *config.ConfigMap) []obsidian.Handler
// GetStreamerProviders returns streamer streams to expose to gateways.
// These stream providers are the primary mechanism by which gateways
// receive data from the orchestrator (e.g. configuration).
GetStreamerProviders() []providers.StreamProvider
// GetStateIndexers returns a list of Indexers to register with the state service.
// These indexers are responsible for generating secondary indices mapped to derived state.
GetStateIndexers() []indexer.Indexer
}
// LoadAllPluginsFatalOnError loads and registers all orchestrator plugins
// and calls os.Exit() on error. See LoadAllPlugins for additional
// documentation.
func LoadAllPluginsFatalOnError(loader OrchestratorPluginLoader) {
if err := LoadAllPlugins(loader); err != nil {
glog.Fatal(err)
}
}
// LoadAllPlugins loads and registers all orchestrator plugins, returning the
// first error encountered during the process. Standard use-cases should pass
// DefaultOrchestratorPluginLoader.
//
// This function will NOT roll back registered plugins if it fails in the
// middle of execution. For this reason, you will likely prefer to use
// LoadAllPluginsFatalOnError which wraps this function with a glog.Fatal.
func LoadAllPlugins(loader OrchestratorPluginLoader) error {
plugins, err := loader.LoadPlugins()
if err != nil {
return err
}
metricsConfig, err := config.GetServiceConfig(orc8r.ModuleName, metricsd.ServiceName)
if err != nil {
return err
}
for _, p := range plugins {
if err := registerPlugin(p, metricsConfig); err != nil {
return err
}
}
return nil
}
// OrchestratorPluginLoader wraps the loading of OrchestratorPlugin impls.
// Standard use case is to use the provided DefaultOrchestratorPluginLoader
// in this package - only create a new impl if you need to customize the
// loading process in some way (e.g. loading from a different directory).
type OrchestratorPluginLoader interface {
LoadPlugins() ([]OrchestratorPlugin, error)
}
// DefaultOrchestratorPluginLoader looks for all .so files in
// /var/opt/magma/plugins and tries to load each .so as an OrchestratorPlugin.
type DefaultOrchestratorPluginLoader struct{}
func (DefaultOrchestratorPluginLoader) LoadPlugins() ([]OrchestratorPlugin, error) {
var ret []OrchestratorPlugin
_, err := os.Stat(modulePluginDir)
if err != nil {
// No plugins to load
if os.IsNotExist(err) {
return ret, nil
}
return ret, fmt.Errorf("failed to stat plugin directory: %s", err)
}
files, err := ioutil.ReadDir(modulePluginDir)
if err != nil {
return ret, fmt.Errorf("failed to read plugin directory contents: %s", err)
}
for _, file := range files {
if file.IsDir() {
continue
}
isPlugin := strings.HasSuffix(file.Name(), ".so")
if !isPlugin {
glog.Infof("Not loading file %s in plugin directory because it does not appear to be a valid plugin", file.Name())
continue
}
p, err := plugin.Open(modulePluginDir + file.Name())
if err != nil {
return []OrchestratorPlugin{}, fmt.Errorf("could not open plugin %s: %s", file.Name(), err)
}
pluginFactory, err := p.Lookup(moduleFactoryFunction)
if err != nil {
return []OrchestratorPlugin{}, fmt.Errorf(
"failed lookup for plugin factory function %s for plugin %s: %s",
moduleFactoryFunction, file.Name(), err,
)
}
castedPluginFactory, ok := pluginFactory.(func() OrchestratorPlugin)
if !ok {
return []OrchestratorPlugin{}, fmt.Errorf(
"failed to cast plugin factory function from plugin %s. Expected func() OrchestratorPlugin, got %s",
file.Name(), reflect.TypeOf(pluginFactory),
)
}
ret = append(ret, castedPluginFactory())
}
return ret, nil
}
func registerPlugin(plug OrchestratorPlugin, metricsConfig *config.ConfigMap) error {
if err := serde.RegisterSerdes(plug.GetSerdes()...); err != nil {
return err
}
if err := obsidian.RegisterAll(plug.GetObsidianHandlers(metricsConfig)); err != nil {
return err
}
configurator.RegisterMconfigBuilders(plug.GetMconfigBuilders()...)
return nil
}