Add v2 server config support with plugin URIs
Closes #3210 Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
42f4bb98ac
commit
9547d269a1
@ -74,6 +74,11 @@ script:
|
|||||||
- go build -i .
|
- go build -i .
|
||||||
- make check
|
- make check
|
||||||
- if [ "$GOOS" = "linux" ]; then make check-protos check-api-descriptors; fi
|
- if [ "$GOOS" = "linux" ]; then make check-protos check-api-descriptors; fi
|
||||||
|
- |
|
||||||
|
sudo mkdir -p /etc/containerd
|
||||||
|
sudo bash -c "cat > /etc/containerd/config.toml <<EOF
|
||||||
|
version = 1
|
||||||
|
EOF"
|
||||||
- make build
|
- make build
|
||||||
- make binaries
|
- make binaries
|
||||||
- if [ "$TRAVIS_GOOS" = "linux" ]; then sudo make install ; fi
|
- if [ "$TRAVIS_GOOS" = "linux" ]; then sudo make install ; fi
|
||||||
@ -86,6 +91,7 @@ script:
|
|||||||
if [ "$TRAVIS_GOOS" = "linux" ]; then
|
if [ "$TRAVIS_GOOS" = "linux" ]; then
|
||||||
sudo mkdir -p /etc/containerd
|
sudo mkdir -p /etc/containerd
|
||||||
sudo bash -c "cat > /etc/containerd/config.toml <<EOF
|
sudo bash -c "cat > /etc/containerd/config.toml <<EOF
|
||||||
|
version = 1
|
||||||
[plugins.cri.containerd.default_runtime]
|
[plugins.cri.containerd.default_runtime]
|
||||||
runtime_type = \"${TEST_RUNTIME}\"
|
runtime_type = \"${TEST_RUNTIME}\"
|
||||||
EOF"
|
EOF"
|
||||||
|
@ -390,6 +390,10 @@ func createShimDebugConfig() string {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
if _, err := f.WriteString("version = 1\n"); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := f.WriteString("[plugins.linux]\n\tshim_debug = true\n"); err != nil {
|
if _, err := f.WriteString("[plugins.linux]\n\tshim_debug = true\n"); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
|
fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
|
||||||
|
@ -60,7 +60,7 @@ var configCommand = cli.Command{
|
|||||||
if p.Config == nil {
|
if p.Config == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
config.Plugins[p.ID] = p.Config
|
config.Plugins[p.URI()] = p.Config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = config.WriteTo(os.Stdout)
|
_, err = config.WriteTo(os.Stdout)
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
func defaultConfig() *srvconfig.Config {
|
func defaultConfig() *srvconfig.Config {
|
||||||
return &srvconfig.Config{
|
return &srvconfig.Config{
|
||||||
|
Version: 2,
|
||||||
Root: defaults.DefaultRootDir,
|
Root: defaults.DefaultRootDir,
|
||||||
State: defaults.DefaultStateDir,
|
State: defaults.DefaultStateDir,
|
||||||
GRPC: srvconfig.GRPCConfig{
|
GRPC: srvconfig.GRPCConfig{
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
func defaultConfig() *srvconfig.Config {
|
func defaultConfig() *srvconfig.Config {
|
||||||
return &srvconfig.Config{
|
return &srvconfig.Config{
|
||||||
|
Version: 2,
|
||||||
Root: defaults.DefaultRootDir,
|
Root: defaults.DefaultRootDir,
|
||||||
State: defaults.DefaultStateDir,
|
State: defaults.DefaultStateDir,
|
||||||
GRPC: srvconfig.GRPCConfig{
|
GRPC: srvconfig.GRPCConfig{
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
func defaultConfig() *srvconfig.Config {
|
func defaultConfig() *srvconfig.Config {
|
||||||
return &srvconfig.Config{
|
return &srvconfig.Config{
|
||||||
|
Version: 2,
|
||||||
Root: defaults.DefaultRootDir,
|
Root: defaults.DefaultRootDir,
|
||||||
State: defaults.DefaultStateDir,
|
State: defaults.DefaultStateDir,
|
||||||
GRPC: srvconfig.GRPCConfig{
|
GRPC: srvconfig.GRPCConfig{
|
||||||
|
@ -127,6 +127,7 @@ func TestDaemonRuntimeRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
configTOML := `
|
configTOML := `
|
||||||
|
version = 1
|
||||||
[plugins]
|
[plugins]
|
||||||
[plugins.cri]
|
[plugins.cri]
|
||||||
stream_server_port = "0"
|
stream_server_port = "0"
|
||||||
@ -221,6 +222,7 @@ func TestDaemonCustomCgroup(t *testing.T) {
|
|||||||
|
|
||||||
customCgroup := fmt.Sprintf("%d", time.Now().Nanosecond())
|
customCgroup := fmt.Sprintf("%d", time.Now().Nanosecond())
|
||||||
configTOML := `
|
configTOML := `
|
||||||
|
version = 1
|
||||||
[cgroup]
|
[cgroup]
|
||||||
path = "` + customCgroup + `"`
|
path = "` + customCgroup + `"`
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ var (
|
|||||||
ErrNoType = errors.New("plugin: no type")
|
ErrNoType = errors.New("plugin: no type")
|
||||||
// ErrNoPluginID is returned when no id is specified
|
// ErrNoPluginID is returned when no id is specified
|
||||||
ErrNoPluginID = errors.New("plugin: no id")
|
ErrNoPluginID = errors.New("plugin: no id")
|
||||||
|
// ErrIDRegistered is returned when a duplicate id is already registered
|
||||||
|
ErrIDRegistered = errors.New("plugin: id already registered")
|
||||||
// ErrSkipPlugin is used when a plugin is not initialized and should not be loaded,
|
// ErrSkipPlugin is used when a plugin is not initialized and should not be loaded,
|
||||||
// this allows the plugin loader differentiate between a plugin which is configured
|
// this allows the plugin loader differentiate between a plugin which is configured
|
||||||
// not to load and one that fails to load.
|
// not to load and one that fails to load.
|
||||||
@ -100,6 +101,8 @@ type Registration struct {
|
|||||||
// context are passed in. The init function may modify the registration to
|
// context are passed in. The init function may modify the registration to
|
||||||
// add exports, capabilities and platform support declarations.
|
// add exports, capabilities and platform support declarations.
|
||||||
InitFn func(*InitContext) (interface{}, error)
|
InitFn func(*InitContext) (interface{}, error)
|
||||||
|
// Disable the plugin from loading
|
||||||
|
Disable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init the registered plugin
|
// Init the registered plugin
|
||||||
@ -157,12 +160,16 @@ func Load(path string) (err error) {
|
|||||||
func Register(r *Registration) {
|
func Register(r *Registration) {
|
||||||
register.Lock()
|
register.Lock()
|
||||||
defer register.Unlock()
|
defer register.Unlock()
|
||||||
|
|
||||||
if r.Type == "" {
|
if r.Type == "" {
|
||||||
panic(ErrNoType)
|
panic(ErrNoType)
|
||||||
}
|
}
|
||||||
if r.ID == "" {
|
if r.ID == "" {
|
||||||
panic(ErrNoPluginID)
|
panic(ErrNoPluginID)
|
||||||
}
|
}
|
||||||
|
if err := checkUnique(r); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
var last bool
|
var last bool
|
||||||
for _, requires := range r.Requires {
|
for _, requires := range r.Requires {
|
||||||
@ -177,24 +184,36 @@ func Register(r *Registration) {
|
|||||||
register.r = append(register.r, r)
|
register.r = append(register.r, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkUnique(r *Registration) error {
|
||||||
|
for _, registered := range register.r {
|
||||||
|
if r.URI() == registered.URI() {
|
||||||
|
return errors.Wrap(ErrIDRegistered, r.URI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableFilter filters out disabled plugins
|
||||||
|
type DisableFilter func(r *Registration) bool
|
||||||
|
|
||||||
// Graph returns an ordered list of registered plugins for initialization.
|
// Graph returns an ordered list of registered plugins for initialization.
|
||||||
// Plugins in disableList specified by id will be disabled.
|
// Plugins in disableList specified by id will be disabled.
|
||||||
func Graph(disableList []string) (ordered []*Registration) {
|
func Graph(filter DisableFilter) (ordered []*Registration) {
|
||||||
register.RLock()
|
register.RLock()
|
||||||
defer register.RUnlock()
|
defer register.RUnlock()
|
||||||
for _, d := range disableList {
|
|
||||||
for i, r := range register.r {
|
for _, r := range register.r {
|
||||||
if r.ID == d {
|
if filter(r) {
|
||||||
register.r = append(register.r[:i], register.r[i+1:]...)
|
r.Disable = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
added := map[*Registration]bool{}
|
added := map[*Registration]bool{}
|
||||||
for _, r := range register.r {
|
for _, r := range register.r {
|
||||||
|
if r.Disable {
|
||||||
children(r.ID, r.Requires, added, &ordered)
|
continue
|
||||||
|
}
|
||||||
|
children(r, added, &ordered)
|
||||||
if !added[r] {
|
if !added[r] {
|
||||||
ordered = append(ordered, r)
|
ordered = append(ordered, r)
|
||||||
added[r] = true
|
added[r] = true
|
||||||
@ -203,11 +222,13 @@ func Graph(disableList []string) (ordered []*Registration) {
|
|||||||
return ordered
|
return ordered
|
||||||
}
|
}
|
||||||
|
|
||||||
func children(id string, types []Type, added map[*Registration]bool, ordered *[]*Registration) {
|
func children(reg *Registration, added map[*Registration]bool, ordered *[]*Registration) {
|
||||||
for _, t := range types {
|
for _, t := range reg.Requires {
|
||||||
for _, r := range register.r {
|
for _, r := range register.r {
|
||||||
if r.ID != id && (t == "*" || r.Type == t) {
|
if !r.Disable &&
|
||||||
children(r.ID, r.Requires, added, ordered)
|
r.URI() != reg.URI() &&
|
||||||
|
(t == "*" || r.Type == t) {
|
||||||
|
children(r, added, ordered)
|
||||||
if !added[r] {
|
if !added[r] {
|
||||||
*ordered = append(*ordered, r)
|
*ordered = append(*ordered, r)
|
||||||
added[r] = true
|
added[r] = true
|
||||||
|
@ -17,13 +17,18 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config provides containerd configuration data for the server
|
// Config provides containerd configuration data for the server
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// Version of the config file
|
||||||
|
Version int `toml:"version"`
|
||||||
// Root is the path to a directory where containerd will store persistent data
|
// Root is the path to a directory where containerd will store persistent data
|
||||||
Root string `toml:"root"`
|
Root string `toml:"root"`
|
||||||
// State is the path to a directory where containerd will store transient data
|
// State is the path to a directory where containerd will store transient data
|
||||||
@ -54,6 +59,42 @@ type Config struct {
|
|||||||
md toml.MetaData
|
md toml.MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVersion returns the config file's version
|
||||||
|
func (c *Config) GetVersion() int {
|
||||||
|
if c.Version == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return c.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateV2 validates the config for a v2 file
|
||||||
|
func (c *Config) ValidateV2() error {
|
||||||
|
if c.GetVersion() != 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, p := range c.DisabledPlugins {
|
||||||
|
if len(strings.Split(p, ".")) < 4 {
|
||||||
|
return errors.Errorf("invalid disabled plugin URI %q expect io.containerd.x.vx", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range c.RequiredPlugins {
|
||||||
|
if len(strings.Split(p, ".")) < 4 {
|
||||||
|
return errors.Errorf("invalid required plugin URI %q expect io.containerd.x.vx", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for p := range c.Plugins {
|
||||||
|
if len(strings.Split(p, ".")) < 4 {
|
||||||
|
return errors.Errorf("invalid plugin key URI %q expect io.containerd.x.vx", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for p := range c.ProxyPlugins {
|
||||||
|
if len(strings.Split(p, ".")) < 4 {
|
||||||
|
return errors.Errorf("invalid proxy plugin key URI %q expect io.containerd.x.vx", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GRPCConfig provides GRPC configuration for the socket
|
// GRPCConfig provides GRPC configuration for the socket
|
||||||
type GRPCConfig struct {
|
type GRPCConfig struct {
|
||||||
Address string `toml:"address"`
|
Address string `toml:"address"`
|
||||||
@ -130,15 +171,19 @@ func (bc *BoltConfig) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode unmarshals a plugin specific configuration by plugin id
|
// Decode unmarshals a plugin specific configuration by plugin id
|
||||||
func (c *Config) Decode(id string, v interface{}) (interface{}, error) {
|
func (c *Config) Decode(p *plugin.Registration) (interface{}, error) {
|
||||||
|
id := p.URI()
|
||||||
|
if c.GetVersion() == 1 {
|
||||||
|
id = p.ID
|
||||||
|
}
|
||||||
data, ok := c.Plugins[id]
|
data, ok := c.Plugins[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return v, nil
|
return p.Config, nil
|
||||||
}
|
}
|
||||||
if err := c.md.PrimitiveDecode(data, v); err != nil {
|
if err := c.md.PrimitiveDecode(data, p.Config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return v, nil
|
return p.Config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads the containerd server config from the provided path
|
// LoadConfig loads the containerd server config from the provided path
|
||||||
@ -151,5 +196,29 @@ func LoadConfig(path string, v *Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.md = md
|
v.md = md
|
||||||
return nil
|
return v.ValidateV2()
|
||||||
|
}
|
||||||
|
|
||||||
|
// V1DisabledFilter matches based on ID
|
||||||
|
func V1DisabledFilter(list []string) plugin.DisableFilter {
|
||||||
|
set := make(map[string]struct{}, len(list))
|
||||||
|
for _, l := range list {
|
||||||
|
set[l] = struct{}{}
|
||||||
|
}
|
||||||
|
return func(r *plugin.Registration) bool {
|
||||||
|
_, ok := set[r.ID]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// V2DisabledFilter matches based on URI
|
||||||
|
func V2DisabledFilter(list []string) plugin.DisableFilter {
|
||||||
|
set := make(map[string]struct{}, len(list))
|
||||||
|
for _, l := range list {
|
||||||
|
set[l] = struct{}{}
|
||||||
|
}
|
||||||
|
return func(r *plugin.Registration) bool {
|
||||||
|
_, ok := set[r.URI()]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,10 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
|||||||
}
|
}
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
id := p.URI()
|
id := p.URI()
|
||||||
|
reqID := id
|
||||||
|
if config.GetVersion() == 1 {
|
||||||
|
reqID = p.ID
|
||||||
|
}
|
||||||
log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id)
|
log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id)
|
||||||
|
|
||||||
initContext := plugin.NewContext(
|
initContext := plugin.NewContext(
|
||||||
@ -140,11 +144,11 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
|||||||
|
|
||||||
// load the plugin specific configuration if it is provided
|
// load the plugin specific configuration if it is provided
|
||||||
if p.Config != nil {
|
if p.Config != nil {
|
||||||
pluginConfig, err := config.Decode(p.ID, p.Config)
|
pc, err := config.Decode(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
initContext.Config = pluginConfig
|
initContext.Config = pc
|
||||||
}
|
}
|
||||||
result := p.Init(initContext)
|
result := p.Init(initContext)
|
||||||
if err := initialized.Add(result); err != nil {
|
if err := initialized.Add(result); err != nil {
|
||||||
@ -158,12 +162,13 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
|||||||
} else {
|
} else {
|
||||||
log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id)
|
log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id)
|
||||||
}
|
}
|
||||||
if _, ok := required[p.ID]; ok {
|
if _, ok := required[reqID]; ok {
|
||||||
return nil, errors.Wrapf(err, "load required plugin %s", id)
|
return nil, errors.Wrapf(err, "load required plugin %s", id)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delete(required, p.ID)
|
|
||||||
|
delete(required, reqID)
|
||||||
// check for grpc services that should be registered with the server
|
// check for grpc services that should be registered with the server
|
||||||
if src, ok := instance.(plugin.Service); ok {
|
if src, ok := instance.(plugin.Service); ok {
|
||||||
grpcServices = append(grpcServices, src)
|
grpcServices = append(grpcServices, src)
|
||||||
@ -266,7 +271,7 @@ func (s *Server) Stop() {
|
|||||||
p := s.plugins[i]
|
p := s.plugins[i]
|
||||||
instance, err := p.Instance()
|
instance, err := p.Instance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.L.WithError(err).WithField("id", p.Registration.ID).
|
log.L.WithError(err).WithField("id", p.Registration.URI()).
|
||||||
Errorf("could not get plugin instance")
|
Errorf("could not get plugin instance")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -275,7 +280,7 @@ func (s *Server) Stop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := closer.Close(); err != nil {
|
if err := closer.Close(); err != nil {
|
||||||
log.L.WithError(err).WithField("id", p.Registration.ID).
|
log.L.WithError(err).WithField("id", p.Registration.URI()).
|
||||||
Errorf("failed to close plugin")
|
Errorf("failed to close plugin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,8 +420,12 @@ func LoadPlugins(ctx context.Context, config *srvconfig.Config) ([]*plugin.Regis
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter := srvconfig.V2DisabledFilter
|
||||||
|
if config.GetVersion() == 1 {
|
||||||
|
filter = srvconfig.V1DisabledFilter
|
||||||
|
}
|
||||||
// return the ordered graph for plugins
|
// return the ordered graph for plugins
|
||||||
return plugin.Graph(config.DisabledPlugins), nil
|
return plugin.Graph(filter(config.DisabledPlugins)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type proxyClients struct {
|
type proxyClients struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user