diff --git a/cmd/cli/plugin.go b/cmd/cli/plugin.go index f818be4dc..98488c211 100644 --- a/cmd/cli/plugin.go +++ b/cmd/cli/plugin.go @@ -52,7 +52,7 @@ func pluginCommand(plugins func() discovery.Plugins) *cobra.Command { } configURL := start.Flags().String("config-url", "", "URL for the startup configs") - osExec := start.Flags().Bool("os", false, "True to use os plugin binaries") + executor := start.Flags().String("exec", "os", "Executor to use for starting up plugins: [os | docker-run]") doWait := start.Flags().BoolP("wait", "w", false, "True to wait in the foreground; Ctrl-C to exit") start.RunE = func(c *cobra.Command, args []string) error { @@ -79,8 +79,16 @@ func pluginCommand(plugins func() discovery.Plugins) *cobra.Command { monitors := []*launch.Monitor{} - if *osExec { - exec, err := os.NewLauncher() + switch *executor { + case "os": + exec, err := os.NewLauncher("os") + if err != nil { + return err + } + monitors = append(monitors, launch.NewMonitor(exec, parsedRules)) + case "docker-run": + // docker-run is also implemented by the same os executor. We just search for a different key (docker-run) + exec, err := os.NewLauncher("docker-run") if err != nil { return err } diff --git a/examples/flavor/swarm/e2e-plugins.json b/examples/flavor/swarm/e2e-plugins.json index b6c1e79fd..ac4f97e37 100644 --- a/examples/flavor/swarm/e2e-plugins.json +++ b/examples/flavor/swarm/e2e-plugins.json @@ -2,8 +2,7 @@ { "Plugin" : "group-default", "Launch" : { - "Exec" : "os", - "Properties": { + "os": { "Cmd" : "infrakit-group-default --poll-interval 500ms --name group-stateless --log 5 > {{env "LOG_DIR"}}/group-default-{{unixtime}}.log 2>&1 &", "SamePgID" : true } @@ -13,8 +12,7 @@ { "Plugin" : "instance-file", "Launch" : { - "Exec" : "os", - "Properties" : { + "os" : { "Cmd" : "infrakit-instance-file --dir {{env "TUTORIAL_DIR"}} --log 5 > {{env "LOG_DIR"}}/instance-file-{{unixtime}}.log 2>&1", "SamePgID" : true } @@ -24,8 +22,7 @@ { "Plugin" : "instance-vagrant", "Launch" : { - "Exec" : "os", - "Properties" : { + "os" : { "Cmd" : "infrakit-instance-vagrant --log 5 > {{env "LOG_DIR"}}/instance-vagrant-{{unixtime}}.log 2>&1", "SamePgID" : true } @@ -35,8 +32,7 @@ { "Plugin" : "flavor-vanilla", "Launch" : { - "Exec" : "os", - "Properties" : { + "os" : { "Cmd" : "infrakit-flavor-vanilla --log 5 > {{env "LOG_DIR"}}/flavor-vanilla-{{unixtime}}.log 2>&1", "SamePgID" : true } @@ -46,8 +42,7 @@ { "Plugin" : "flavor-swarm", "Launch" : { - "Exec" : "os", - "Properties" : { + "os" : { "Cmd" : "infrakit-flavor-swarm --host {{env "SWARM_MANAGER"}} --log 5 > {{env "LOG_DIR"}}/flavor-swarm-{{unixtime}}.log 2>&1", "SamePgID" : true } diff --git a/pkg/launch/monitor.go b/pkg/launch/monitor.go index f7cc9334e..1b74bb64a 100644 --- a/pkg/launch/monitor.go +++ b/pkg/launch/monitor.go @@ -11,13 +11,8 @@ import ( var errNoConfig = errors.New("no-config") -// ExecRule encapsulates what's required to exec a plugin -type ExecRule struct { - // Exec is the name of the exec to use to start the plugin - Exec string - // Properties is the properties for the executor - Properties *types.Any -} +// ExecName is the name of the executor to use (e.g. 'os', 'docker-run', etc.). It's found in the config. +type ExecName string // Rule provides the instructions on starting the plugin type Rule struct { @@ -25,15 +20,16 @@ type Rule struct { // Plugin is the name of the plugin Plugin plugin.Name - // Launch is the rule for starting / launching the plugin. - Launch ExecRule + // Launch is the rule for starting / launching the plugin. It's a dictionary with the key being + // the name of the executor and the value being the properties used by that executor. + Launch map[ExecName]*types.Any } // Monitor runs continuously receiving requests to start a plugin. // Monitor uses a launcher to actually start the process of the plugin. type Monitor struct { exec Exec - rules map[plugin.Name]Rule + rules map[plugin.Name]*types.Any startChan <-chan StartPlugin inputChan chan<- StartPlugin stop chan interface{} @@ -42,12 +38,13 @@ type Monitor struct { // NewMonitor returns a monitor that continuously watches for input // requests and launches the process for the plugin, if not already running. +// The configuration to use in the config is matched to the Name() of the executor (the field Exec). func NewMonitor(l Exec, rules []Rule) *Monitor { - m := map[plugin.Name]Rule{} + m := map[plugin.Name]*types.Any{} // index by name of plugin for _, r := range rules { - if r.Launch.Exec == l.Name() { - m[r.Plugin] = r + if cfg, has := r.Launch[ExecName(l.Name())]; has { + m[r.Plugin] = cfg } } return &Monitor{ @@ -100,30 +97,30 @@ func (m *Monitor) Start() (chan<- StartPlugin, error) { } // match first by full name of the form lookup/type -- 'specialization' - r, has := m.rules[req.Plugin] + properties, has := m.rules[req.Plugin] if !has { // match now by lookup only -- 'base class' alternate, _ := req.Plugin.GetLookupAndType() - r, has = m.rules[plugin.Name(alternate)] + properties, has = m.rules[plugin.Name(alternate)] } if !has { log.Warningln("no plugin:", req) - req.reportError(r.Launch.Properties, errNoConfig) + req.reportError(nil, errNoConfig) continue loop } configCopy := types.AnyBytes(nil) - if r.Launch.Properties != nil { - *configCopy = *r.Launch.Properties + if properties != nil { + *configCopy = *properties } - block, err := m.exec.Exec(r.Plugin.String(), configCopy) + block, err := m.exec.Exec(req.Plugin.String(), configCopy) if err != nil { req.reportError(configCopy, err) continue loop } - log.Infoln("Waiting for", r.Plugin, "to start:", configCopy.String()) + log.Infoln("Waiting for", req.Plugin, "to start:", configCopy.String()) err = <-block if err != nil { req.reportError(configCopy, err) diff --git a/pkg/launch/monitor_test.go b/pkg/launch/monitor_test.go index de05f0f02..012e1e69b 100644 --- a/pkg/launch/monitor_test.go +++ b/pkg/launch/monitor_test.go @@ -69,9 +69,8 @@ func TestMonitorLoopValidRule(t *testing.T) { var receivedArgs *types.Any rule := Rule{ Plugin: "hello", - Launch: ExecRule{ - Exec: "test", - Properties: types.AnyValueMust(config), + Launch: map[ExecName]*types.Any{ + "test": types.AnyValueMust(config), }, } monitor := NewMonitor(&testLauncher{ @@ -112,9 +111,8 @@ func TestMonitorLoopRuleLookupBehavior(t *testing.T) { var receivedArgs *types.Any rule := Rule{ Plugin: "hello", - Launch: ExecRule{ - Exec: "test", - Properties: types.AnyValueMust(config), + Launch: map[ExecName]*types.Any{ + "test": types.AnyValueMust(config), }, } monitor := NewMonitor(&testLauncher{ diff --git a/pkg/launch/os/os.go b/pkg/launch/os/os.go index 2ab5adc02..49c3c74cc 100644 --- a/pkg/launch/os/os.go +++ b/pkg/launch/os/os.go @@ -19,8 +19,9 @@ type LaunchConfig struct { // NewLauncher returns a Launcher that can install and start plugins. The OS version is simple - it translates // plugin names as command names and uses os.Exec -func NewLauncher() (*Launcher, error) { +func NewLauncher(n string) (*Launcher, error) { return &Launcher{ + name: n, plugins: map[string]state{}, }, nil } @@ -31,13 +32,14 @@ type state struct { // Launcher is a service that implements the launch.Exec interface for starting up os processes. type Launcher struct { + name string plugins map[string]state lock sync.Mutex } // Name returns the name of the launcher func (l *Launcher) Name() string { - return "os" + return l.name } // Exec starts the os process. Returns a signal channel to block on optionally. diff --git a/pkg/launch/os/os_test.go b/pkg/launch/os/os_test.go index f56a8a97c..5a53720fb 100644 --- a/pkg/launch/os/os_test.go +++ b/pkg/launch/os/os_test.go @@ -14,7 +14,7 @@ import ( func TestLaunchOSCommand(t *testing.T) { - launcher, err := NewLauncher() + launcher, err := NewLauncher("os") require.NoError(t, err) starting, err := launcher.Exec("sleepPlugin", types.AnyValueMust(&LaunchConfig{ @@ -30,7 +30,7 @@ func TestLaunchWithLog(t *testing.T) { logfile := filepath.Join(os.TempDir(), fmt.Sprintf("os-test-%v", time.Now().Unix())) - launcher, err := NewLauncher() + launcher, err := NewLauncher("os") require.NoError(t, err) starting, err := launcher.Exec("echoPlugin", types.AnyValueMust(&LaunchConfig{ diff --git a/scripts/e2e-test-plugins.json b/scripts/e2e-test-plugins.json index b6c1e79fd..3e1eab547 100644 --- a/scripts/e2e-test-plugins.json +++ b/scripts/e2e-test-plugins.json @@ -1,22 +1,27 @@ +{{define "rundocker"}}docker run -d --restart always -e INFRAKIT_HOME=/infrakit -e INFRAKIT_PLUGINS_DIR=/infrakit/plugins -v ~/.infrakit:/infrakit --name {{.}} infrakit/devbundle{{end}} [ { - "Plugin" : "group-default", + "Plugin" : "group-stateless", "Launch" : { - "Exec" : "os", - "Properties": { - "Cmd" : "infrakit-group-default --poll-interval 500ms --name group-stateless --log 5 > {{env "LOG_DIR"}}/group-default-{{unixtime}}.log 2>&1 &", + "os": { + "Cmd" : "infrakit-group-default --poll-interval 500ms --name group-stateless --log 5 > {{env "INFRAKIT_HOME"}}/logs/group-stateless.log 2>&1", "SamePgID" : true - } + }, + "docker-run" : { + "Cmd" : "{{template "rundocker" "group-stateless"}} infrakit-group-default --poll-interval 500ms --name group-stateless --log 5" + } } } , { "Plugin" : "instance-file", "Launch" : { - "Exec" : "os", - "Properties" : { - "Cmd" : "infrakit-instance-file --dir {{env "TUTORIAL_DIR"}} --log 5 > {{env "LOG_DIR"}}/instance-file-{{unixtime}}.log 2>&1", + "os" : { + "Cmd" : "infrakit-instance-file --dir {{env "INFRAKIT_HOME"}}/instance-file --log 5 > {{env "INFRAKIT_HOME"}}/logs/instance-file.log 2>&1", "SamePgID" : true + }, + "docker-run" : { + "Cmd" : "{{template "rundocker" "instance-file"}} infrakit-instance-file --dir {{env "INFRAKIT_HOME"}}/instance-file --log 5" } } } @@ -24,10 +29,12 @@ { "Plugin" : "instance-vagrant", "Launch" : { - "Exec" : "os", - "Properties" : { - "Cmd" : "infrakit-instance-vagrant --log 5 > {{env "LOG_DIR"}}/instance-vagrant-{{unixtime}}.log 2>&1", + "os" : { + "Cmd" : "infrakit-instance-vagrant --log 5 > {{env "INFRAKIT_HOME"}}/logs/instance-vagrant.log 2>&1", "SamePgID" : true + }, + "docker-run" : { + "Cmd" : "{{template "rundocker" "instance-vagrant"}} infrakit-instance-vagrant --log 5" } } } @@ -35,10 +42,12 @@ { "Plugin" : "flavor-vanilla", "Launch" : { - "Exec" : "os", - "Properties" : { - "Cmd" : "infrakit-flavor-vanilla --log 5 > {{env "LOG_DIR"}}/flavor-vanilla-{{unixtime}}.log 2>&1", + "os" : { + "Cmd" : "infrakit-flavor-vanilla --log 5 > {{env "INFRAKIT_HOME"}}/logs/flavor-vanilla.log 2>&1", "SamePgID" : true + }, + "docker-run" : { + "Cmd" : "{{template "rundocker" "flavor-vanilla"}} infrakit-flavor-vanilla --log 5" } } } @@ -46,10 +55,12 @@ { "Plugin" : "flavor-swarm", "Launch" : { - "Exec" : "os", - "Properties" : { - "Cmd" : "infrakit-flavor-swarm --host {{env "SWARM_MANAGER"}} --log 5 > {{env "LOG_DIR"}}/flavor-swarm-{{unixtime}}.log 2>&1", + "os" : { + "Cmd" : "infrakit-flavor-swarm --host {{env "SWARM_MANAGER"}} --log 5 > {{env "INFRAKIT_HOME"}}/logs/flavor-swarm.log 2>&1", "SamePgID" : true + }, + "docker-run" : { + "Cmd" : "{{template "rundocker" "flavor-swarm"}} infrakit-flavor-swarm --host {{env "SWARM_MANAGER"}} --log 5" } } } diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh index b79e6ca2c..ab4f510ef 100755 --- a/scripts/e2e-test.sh +++ b/scripts/e2e-test.sh @@ -24,16 +24,19 @@ cleanup() { trap cleanup EXIT # infrakit directories -plugins=~/.infrakit/plugins + +export INFRAKIT_HOME=~/.infrakit + +plugins=$INFRAKIT_HOME/plugins mkdir -p $plugins rm -rf $plugins/* -configstore=~/.infrakit/configs +configstore=$INFRAKIT_HOME/configs mkdir -p $configstore rm -rf $configstore/* # set the leader -- for os / file based leader detection for manager -leaderfile=~/.infrakit/leader +leaderfile=$INFRAKIT_HOME/leader echo group > $leaderfile # start up multiple instances of manager -- typically we want multiple SETS of plugins and managers @@ -46,19 +49,18 @@ sleep 5 # manager needs to detect leadership # location of logfiles when plugins are started by the plugin cli # the config json below expects LOG_DIR as an environment variable -LOG_DIR=~/.infrakit/logs +LOG_DIR=$INFRAKIT_HOME/logs mkdir -p $LOG_DIR -# see the config josn 'e2e-test-plugins.json' for reference of environment variable TUTORIAL_DIR -TUTORIAL_DIR=~/.infrakit/tutorial -mkdir -p $TUTORIAL_DIR -rm -rf $TUTORIAL_DIR/* +# see the config josn 'e2e-test-plugins.json' for reference of environment variable +INSTANCE_FILE_DIR=$INFRAKIT_HOME/instance-file +mkdir -p $INSTANCE_FILE_DIR +rm -rf $INSTANCE_FILE_DIR/* export LOG_DIR=$LOG_DIR -export TUTORIAL_DIR=$TUTORIAL_DIR # note -- on exit, this won't clean up the plugins started by the cli since they will be in a separate process group -infrakit plugin start --wait --config-url file:///$PWD/scripts/e2e-test-plugins.json --os group-default instance-file flavor-vanilla & +infrakit plugin start --wait --config-url file:///$PWD/scripts/e2e-test-plugins.json --exec os group-stateless instance-file flavor-vanilla & starterpid=$! echo "plugin start pid=$starterpid" @@ -130,7 +132,7 @@ sleep 5 expect_output_lines "10 instances should exist in group" "infrakit group describe cattle -q" "10" # Terminate 3 instances. -pushd $TUTORIAL_DIR +pushd $INSTANCE_FILE_DIR rm $(ls | head -3) popd