Add v2 server config support with plugin URIs

Closes #3210

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2019-06-04 17:55:58 +00:00
parent 42f4bb98ac
commit 9547d269a1
10 changed files with 147 additions and 33 deletions

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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{

View File

@ -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{

View File

@ -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{

View File

@ -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 + `"`

View File

@ -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

View File

@ -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
}
} }

View File

@ -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 {