Skip to content

Commit

Permalink
feature: add plugin framework which supports executing custom code at…
Browse files Browse the repository at this point in the history
… plugin points like start and stop in life cyle of container and daemon
  • Loading branch information
yyb196 committed Mar 19, 2018
1 parent fa50191 commit 75f71ce
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 45 deletions.
22 changes: 22 additions & 0 deletions apis/plugins/ContainerPlugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package plugins

import "io"

// ContainerPlugin defines in which place a plugin will be triggered in container lifecycle
type ContainerPlugin interface {
// PreCreate defines plugin point where recevives an container create request, in this plugin point user
// could change the container create body passed-in by http request body
PreCreate(io.ReadCloser) (io.ReadCloser, error)

// PreStart returns an array of PreStartHook which will pass to runc, in PreStartHook there is a Priority which
// used to sort the pre start array that pass to runc, network plugin hook has priority value 0.
PreStart(interface{}) (PreStartHook, error)
}

// PreStartHook defines the hook wrapper which will return priority and the hook info
type PreStartHook interface {
// Priority returns priority of this hook, the bigger one will run first
Priority() []int
// Hook returns the real hook command
Hook() [][]string
}
12 changes: 12 additions & 0 deletions apis/plugins/DaemonPlugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package plugins

// DaemonPlugin defines in which place does pouch daemon support plugin
type DaemonPlugin interface {
// PreStartHook is invoked by pouch daemon before real start, in this hook user could start dfget proxy or other
// standalone process plugins
PreStartHook() error

// PreStopHook is invoked by pouch daemon before daemon process exit, not a promise if daemon is killed, in this
// hook user could stop the process or plugin started by PreStartHook
PreStopHook() error
}
10 changes: 9 additions & 1 deletion apis/server/container_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/go-openapi/strfmt"
"github.com/gorilla/mux"
"github.com/pkg/errors"
)

func (s *Server) removeContainers(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
Expand Down Expand Up @@ -114,8 +115,15 @@ func (s *Server) startContainerExec(ctx context.Context, rw http.ResponseWriter,

func (s *Server) createContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
config := &types.ContainerCreateConfig{}
reader := req.Body
var ex error
if s.ContainerPlugin != nil {
if reader, ex = s.ContainerPlugin.PreCreate(req.Body); ex != nil {
return errors.Wrapf(ex, "pre-create plugin piont execute failed")
}
}
// decode request body
if err := json.NewDecoder(req.Body).Decode(config); err != nil {
if err := json.NewDecoder(reader).Decode(config); err != nil {
return httputils.NewHTTPError(err, http.StatusBadRequest)
}
// validate request body
Expand Down
16 changes: 9 additions & 7 deletions apis/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"syscall"

"github.com/alibaba/pouch/apis/plugins"
"github.com/alibaba/pouch/daemon/config"
"github.com/alibaba/pouch/daemon/mgr"

Expand All @@ -18,13 +19,14 @@ import (

// Server is a http server which serves restful api to client.
type Server struct {
Config config.Config
ContainerMgr mgr.ContainerMgr
SystemMgr mgr.SystemMgr
ImageMgr mgr.ImageMgr
VolumeMgr mgr.VolumeMgr
NetworkMgr mgr.NetworkMgr
listeners []net.Listener
Config config.Config
ContainerMgr mgr.ContainerMgr
SystemMgr mgr.SystemMgr
ImageMgr mgr.ImageMgr
VolumeMgr mgr.VolumeMgr
NetworkMgr mgr.NetworkMgr
listeners []net.Listener
ContainerPlugin plugins.ContainerPlugin
}

// Start setup route table and listen to specified address which currently only supports unix socket and tcp address.
Expand Down
3 changes: 3 additions & 0 deletions daemon/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,7 @@ type Config struct {

// CgroupParent is to set parent cgroup for all containers
CgroupParent string `json:"cgroup-parent,omitempty"`

// PluginPath is set the path where plugin so file put
PluginPath string `json:"plugin"`
}
95 changes: 78 additions & 17 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package daemon

import (
"context"
"fmt"
"path"
"plugin"
"reflect"

"github.com/alibaba/pouch/apis/plugins"
"github.com/alibaba/pouch/apis/server"
cri "github.com/alibaba/pouch/cri/service"
"github.com/alibaba/pouch/ctrd"
Expand All @@ -13,24 +16,27 @@ import (
"github.com/alibaba/pouch/internal"
"github.com/alibaba/pouch/network/mode"
"github.com/alibaba/pouch/pkg/meta"
"github.com/pkg/errors"

"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)

// Daemon refers to a daemon.
type Daemon struct {
config config.Config
containerStore *meta.Store
containerd *ctrd.Client
containerMgr mgr.ContainerMgr
systemMgr mgr.SystemMgr
imageMgr mgr.ImageMgr
volumeMgr mgr.VolumeMgr
networkMgr mgr.NetworkMgr
criMgr mgr.CriMgr
server server.Server
criService *cri.Service
config config.Config
containerStore *meta.Store
containerd *ctrd.Client
containerMgr mgr.ContainerMgr
systemMgr mgr.SystemMgr
imageMgr mgr.ImageMgr
volumeMgr mgr.VolumeMgr
networkMgr mgr.NetworkMgr
criMgr mgr.CriMgr
server server.Server
criService *cri.Service
containerPlugin plugins.ContainerPlugin
daemonPlugin plugins.DaemonPlugin
}

// router represents the router of daemon.
Expand Down Expand Up @@ -71,11 +77,55 @@ func NewDaemon(cfg config.Config) *Daemon {
}
}

func loadSymbolByName(p *plugin.Plugin, name string) (plugin.Symbol, error) {
s, err := p.Lookup(name)
if err != nil {
return nil, errors.Wrapf(err, "lookup plugin with name %s error", name)
}
return s, nil
}

// Run starts daemon.
func (d *Daemon) Run() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var s plugin.Symbol
var err error

if d.config.PluginPath != "" {
p, err := plugin.Open(d.config.PluginPath)
if err != nil {
return errors.Wrapf(err, "load plugin at %s error", d.config.PluginPath)
}

//load container plugin if exist
if s, err = loadSymbolByName(p, "DaemonPlugin"); err != nil {
return err
}
if daemonPlugin, ok := s.(plugins.DaemonPlugin); ok {
d.daemonPlugin = daemonPlugin
} else if s != nil {
return fmt.Errorf("not a container plugin at %s %q", d.config.PluginPath, s)
}

//load container plugin if exist
if s, err = loadSymbolByName(p, "ContainerPlugin"); err != nil {
return err
}
if containerPlugin, ok := s.(plugins.ContainerPlugin); ok {
d.containerPlugin = containerPlugin
} else if s != nil {
return fmt.Errorf("not a container plugin at %s %q", d.config.PluginPath, s)
}
}

if d.daemonPlugin != nil {
if err = d.daemonPlugin.PreStartHook(); err != nil {
return err
}
}

imageMgr, err := internal.GenImageMgr(&d.config, d)
if err != nil {
return err
Expand Down Expand Up @@ -118,12 +168,13 @@ func (d *Daemon) Run() error {
}

d.server = server.Server{
Config: d.config,
ContainerMgr: containerMgr,
SystemMgr: systemMgr,
ImageMgr: imageMgr,
VolumeMgr: volumeMgr,
NetworkMgr: networkMgr,
Config: d.config,
ContainerMgr: containerMgr,
SystemMgr: systemMgr,
ImageMgr: imageMgr,
VolumeMgr: volumeMgr,
NetworkMgr: networkMgr,
ContainerPlugin: d.containerPlugin,
}

// init base network
Expand Down Expand Up @@ -166,6 +217,11 @@ func (d *Daemon) Run() error {
logrus.Infof("GRPC server stopped")
<-streamServerCloseCh
logrus.Infof("Stream server stopped")
if d.daemonPlugin != nil {
if err = d.daemonPlugin.PreStopHook(); err != nil {
logrus.Errorf("stop prehook execute error %v", err)
}
}
return nil
}

Expand Down Expand Up @@ -212,3 +268,8 @@ func (d *Daemon) MetaStore() *meta.Store {
func (d *Daemon) networkInit(ctx context.Context) error {
return mode.NetworkModeInit(ctx, d.config.NetworkConfg, d.networkMgr)
}

// ContainerPlugin returns the container plugin fetched from shared file
func (d *Daemon) ContainerPlugin() plugins.ContainerPlugin {
return d.containerPlugin
}
45 changes: 29 additions & 16 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"time"

"github.com/alibaba/pouch/apis/plugins"
"github.com/alibaba/pouch/apis/types"
"github.com/alibaba/pouch/ctrd"
"github.com/alibaba/pouch/daemon/config"
Expand Down Expand Up @@ -112,22 +113,25 @@ type ContainerManager struct {

// monitor is used to handle container's event, eg: exit, stop and so on.
monitor *ContainerMonitor

containerPlugin plugins.ContainerPlugin
}

// NewContainerManager creates a brand new container manager.
func NewContainerManager(ctx context.Context, store *meta.Store, cli *ctrd.Client, imgMgr ImageMgr, volMgr VolumeMgr, netMgr NetworkMgr, cfg *config.Config) (*ContainerManager, error) {
func NewContainerManager(ctx context.Context, store *meta.Store, cli *ctrd.Client, imgMgr ImageMgr, volMgr VolumeMgr, netMgr NetworkMgr, cfg *config.Config, contPlugin plugins.ContainerPlugin) (*ContainerManager, error) {
mgr := &ContainerManager{
Store: store,
NameToID: collect.NewSafeMap(),
Client: cli,
ImageMgr: imgMgr,
VolumeMgr: volMgr,
NetworkMgr: netMgr,
IOs: containerio.NewCache(),
ExecProcesses: collect.NewSafeMap(),
cache: collect.NewSafeMap(),
Config: cfg,
monitor: NewContainerMonitor(),
Store: store,
NameToID: collect.NewSafeMap(),
Client: cli,
ImageMgr: imgMgr,
VolumeMgr: volMgr,
NetworkMgr: netMgr,
IOs: containerio.NewCache(),
ExecProcesses: collect.NewSafeMap(),
cache: collect.NewSafeMap(),
Config: cfg,
monitor: NewContainerMonitor(),
containerPlugin: contPlugin,
}

mgr.Client.SetExitHooks(mgr.exitedAndRelease)
Expand Down Expand Up @@ -502,11 +506,20 @@ func (mgr *ContainerManager) Start(ctx context.Context, id, detachKeys string) (
s.Linux.CgroupsPath = filepath.Join(cgroupsParent, c.ID())
}

var customHooks plugins.PreStartHook
if mgr.containerPlugin != nil {
customHooks, err = mgr.containerPlugin.PreStart(c)
if err != nil {
return errors.Wrapf(err, "get pre-start hook error from container plugin")
}
}

sw := &SpecWrapper{
s: s,
ctrMgr: mgr,
volMgr: mgr.VolumeMgr,
netMgr: mgr.NetworkMgr,
s: s,
ctrMgr: mgr,
volMgr: mgr.VolumeMgr,
netMgr: mgr.NetworkMgr,
preStartHooks: customHooks,
}

for _, setup := range SetupFuncs() {
Expand Down
9 changes: 6 additions & 3 deletions daemon/mgr/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package mgr
import (
"context"

"github.com/alibaba/pouch/apis/plugins"

specs "github.com/opencontainers/runtime-spec/specs-go"
)

// SpecWrapper wraps the container's specs and add manager operations.
type SpecWrapper struct {
s *specs.Spec

ctrMgr ContainerMgr
volMgr VolumeMgr
netMgr NetworkMgr
ctrMgr ContainerMgr
volMgr VolumeMgr
netMgr NetworkMgr
preStartHooks plugins.PreStartHook
}

// SetupFunc defines spec setup function type.
Expand Down
Loading

0 comments on commit 75f71ce

Please sign in to comment.