From b23c0bb9f87e37212b2203601348dfb4969a51ee Mon Sep 17 00:00:00 2001 From: David Chung Date: Sat, 21 Oct 2017 03:27:40 -0700 Subject: [PATCH 01/11] Var / metadata fixes for util/init Signed-off-by: David Chung --- cmd/infrakit/util/init/init.go | 215 ++++++++++++++------------------- pkg/run/scope/local/local.go | 112 +++++++++++++++++ pkg/run/scope/scope.go | 26 ++++ pkg/run/template/std.go | 21 +++- pkg/template/template.go | 36 +++--- 5 files changed, 267 insertions(+), 143 deletions(-) create mode 100644 pkg/run/scope/local/local.go create mode 100644 pkg/run/scope/scope.go diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index 5f44b8c7a..12d83d041 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -3,7 +3,6 @@ package init import ( "fmt" "os" - "strings" "time" "github.com/docker/infrakit/pkg/cli" @@ -13,9 +12,9 @@ import ( "github.com/docker/infrakit/pkg/plugin" group_types "github.com/docker/infrakit/pkg/plugin/group/types" flavor_rpc "github.com/docker/infrakit/pkg/rpc/flavor" - run "github.com/docker/infrakit/pkg/run/manager" - group_kind "github.com/docker/infrakit/pkg/run/v0/group" - manager_kind "github.com/docker/infrakit/pkg/run/v0/manager" + "github.com/docker/infrakit/pkg/run/manager" + "github.com/docker/infrakit/pkg/run/scope" + "github.com/docker/infrakit/pkg/run/scope/local" "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/types" @@ -24,8 +23,8 @@ import ( var log = logutil.New("module", "util/init") -func startPlugins(plugins func() discovery.Plugins, services *cli.Services, - configURL string, starts []string) (*run.Manager, error) { +func getPluginManager(plugins func() discovery.Plugins, services *cli.Services, + configURL string, starts []string) (*manager.Manager, error) { parsedRules := []launch.Rule{} @@ -44,44 +43,7 @@ func startPlugins(plugins func() discovery.Plugins, services *cli.Services, return nil, err } } - - pluginManager, err := run.ManagePlugins(parsedRules, plugins, true, 5*time.Second) - if err != nil { - return nil, err - } - - for _, arg := range starts { - - p := strings.Split(arg, "=") - execName := "inproc" // default is to use inprocess goroutine for running plugins - if len(p) > 1 { - execName = p[1] - } - - // the format is kind[:{plugin_name}][={os|inproc}] - pp := strings.Split(p[0], ":") - kind := pp[0] - name := plugin.Name(kind) - - // This is some special case for the legacy setup (pre v0.6) - switch kind { - case manager_kind.Kind: - name = plugin.Name(manager_kind.LookupName) - case group_kind.Kind: - name = plugin.Name(group_kind.LookupName) - } - // customized by user as override - if len(pp) > 1 { - name = plugin.Name(pp[1]) - } - - log.Info("Launching", "kind", kind, "name", name) - err = pluginManager.Launch(execName, kind, name, nil) - if err != nil { - log.Warn("failed to launch", "exec", execName, "kind", kind, "name", name) - } - } - return pluginManager, nil + return manager.ManagePlugins(parsedRules, plugins, true, 5*time.Second) } // Command returns the cobra command @@ -122,107 +84,116 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { wait := types.MustParseDuration(*waitDuration) - pluginManager, err := startPlugins(plugins, services, *configURL, *starts) + pluginManager, err := getPluginManager(plugins, services, *configURL, *starts) if err != nil { return err } - defer func() { - <-time.After(wait.Duration()) - pluginManager.TerminateAll() - pluginManager.WaitForAllShutdown() - pluginManager.Stop() - }() - - pluginManager.WaitStarting() - <-time.After(wait.Duration()) - - input, err := services.ReadFromStdinIfElse( - func() bool { return args[0] == "-" }, - func() (string, error) { return services.ProcessTemplate(args[0]) }, - services.ToJSON, - ) - if err != nil { - log.Error("processing input", "err", err) - return err - } - // TODO - update the schema -- this matches the current Plugin/Properties schema - type spec struct { - Plugin plugin.Name - Properties struct { - ID group.ID - Properties group_types.Spec + buildInit := func(scope scope.Scope) error { + + input, err := services.ReadFromStdinIfElse( + func() bool { return args[0] == "-" }, + func() (string, error) { return services.ProcessTemplate(args[0]) }, + services.ToJSON, + ) + if err != nil { + log.Error("processing input", "err", err) + return err } - } - specs := []spec{} - err = types.AnyString(input).Decode(&specs) - if err != nil { - return err - } + // TODO - update the schema -- this matches the current Plugin/Properties schema + type spec struct { + Plugin plugin.Name + Properties struct { + ID group.ID + Properties group_types.Spec + } + } - var groupSpec *group_types.Spec - for _, s := range specs { - if string(s.Properties.ID) == *groupID { - copy := s.Properties.Properties - groupSpec = © - break + specs := []spec{} + err = types.AnyString(input).Decode(&specs) + if err != nil { + return err } - } - if groupSpec == nil { - return fmt.Errorf("no such group: %v", *groupID) - } + var groupSpec *group_types.Spec + for _, s := range specs { + if string(s.Properties.ID) == *groupID { + copy := s.Properties.Properties + groupSpec = © + break + } + } - // Get the flavor properties and use that to call the prepare of the Flavor to generate the init - endpoint, err := plugins().Find(groupSpec.Flavor.Plugin) - if err != nil { - log.Error("error looking up plugin", "plugin", groupSpec.Flavor.Plugin, "err", err) - return err - } + if groupSpec == nil { + return fmt.Errorf("no such group: %v", *groupID) + } - flavorPlugin, err := flavor_rpc.NewClient(groupSpec.Flavor.Plugin, endpoint.Address) - if err != nil { - return err - } + // Get the flavor properties and use that to call the prepare of the Flavor to generate the init + endpoint, err := scope.Plugins().Find(groupSpec.Flavor.Plugin) + if err != nil { + log.Error("error looking up plugin", "plugin", groupSpec.Flavor.Plugin, "err", err) + return err + } + + flavorPlugin, err := flavor_rpc.NewClient(groupSpec.Flavor.Plugin, endpoint.Address) + if err != nil { + return err + } - cli.MustNotNil(flavorPlugin, "flavor plugin not found", "name", groupSpec.Flavor.Plugin.String()) + cli.MustNotNil(flavorPlugin, "flavor plugin not found", "name", groupSpec.Flavor.Plugin.String()) - instanceSpec := instance.Spec{} - if lidLen := len(groupSpec.Allocation.LogicalIDs); lidLen > 0 { + instanceSpec := instance.Spec{} + if lidLen := len(groupSpec.Allocation.LogicalIDs); lidLen > 0 { - if int(*sequence) >= lidLen { - return fmt.Errorf("out of bound sequence index: %v in %v", *sequence, groupSpec.Allocation.LogicalIDs) + if int(*sequence) >= lidLen { + return fmt.Errorf("out of bound sequence index: %v in %v", *sequence, groupSpec.Allocation.LogicalIDs) + } + + lid := instance.LogicalID(groupSpec.Allocation.LogicalIDs[*sequence]) + instanceSpec.LogicalID = &lid } - lid := instance.LogicalID(groupSpec.Allocation.LogicalIDs[*sequence]) - instanceSpec.LogicalID = &lid - } + instanceSpec, err = flavorPlugin.Prepare(groupSpec.Flavor.Properties, instanceSpec, + groupSpec.Allocation, + group_types.Index{Group: group.ID(*groupID), Sequence: *sequence}) - instanceSpec, err = flavorPlugin.Prepare(groupSpec.Flavor.Properties, instanceSpec, - groupSpec.Allocation, - group_types.Index{Group: group.ID(*groupID), Sequence: *sequence}) + if err != nil { + log.Error("error preparing", "err", err, "spec", instanceSpec) + return err + } - if err != nil { - log.Error("error preparing", "err", err, "spec", instanceSpec) - return err - } + log.Info("apply init template", "init", instanceSpec.Init) - log.Info("apply init template", "init", instanceSpec.Init) + // Here the Init may contain template vars since in the evaluation of the manager / worker + // init templates, we do not propapage the vars set in the command line here. + // So we need to evaluate the entire Init as a template again. + // TODO - this is really better addressed via some formal globally available var store/section + // that is always available to the templates at the schema / document level. + applied, err := services.ProcessTemplate("str://" + instanceSpec.Init) + if err != nil { + return err + } - // Here the Init may contain template vars since in the evaluation of the manager / worker - // init templates, we do not propapage the vars set in the command line here. - // So we need to evaluate the entire Init as a template again. - // TODO - this is really better addressed via some formal globally available var store/section - // that is always available to the templates at the schema / document level. - applied, err := services.ProcessTemplate("str://" + instanceSpec.Init) - if err != nil { - return err + fmt.Print(applied) + + return nil } - fmt.Print(applied) + return local.Execute(plugins, pluginManager, + func() (targets []local.StartPlugin, err error) { + for _, start := range *starts { + targets = append(targets, local.StartPlugin(start)) + } + return + }, + buildInit, + local.Options{ + StartWait: wait, + StopWait: wait, + }, + ) - return nil } return cmd diff --git a/pkg/run/scope/local/local.go b/pkg/run/scope/local/local.go new file mode 100644 index 000000000..b15fb5eb6 --- /dev/null +++ b/pkg/run/scope/local/local.go @@ -0,0 +1,112 @@ +package local + +import ( + "fmt" + "strings" + "time" + + "github.com/docker/infrakit/pkg/discovery" + logutil "github.com/docker/infrakit/pkg/log" + "github.com/docker/infrakit/pkg/plugin" + "github.com/docker/infrakit/pkg/run/manager" + "github.com/docker/infrakit/pkg/run/scope" + group_kind "github.com/docker/infrakit/pkg/run/v0/group" + manager_kind "github.com/docker/infrakit/pkg/run/v0/manager" + "github.com/docker/infrakit/pkg/types" +) + +var ( + log = logutil.New("module", "run/scope/local") + debugV = logutil.V(300) +) + +// StartPlugin is a specification of what plugin to run and what socket name +// to use, etc. +// The format is kind[:{plugin_name}][={os|inproc}] +type StartPlugin string + +// Parse parses the specification into parts that the manager can use to launch plugins +func (arg StartPlugin) Parse() (execName string, kind string, name plugin.Name, err error) { + p := strings.Split(string(arg), "=") + execName = "inproc" // default is to use inprocess goroutine for running plugins + if len(p) > 1 { + execName = p[1] + } + + // the format is kind[:{plugin_name}][={os|inproc}] + pp := strings.Split(p[0], ":") + kind = pp[0] + name = plugin.Name(kind) + + // This is some special case for the legacy setup (pre v0.6) + switch kind { + case manager_kind.Kind: + name = plugin.Name(manager_kind.LookupName) + case group_kind.Kind: + name = plugin.Name(group_kind.LookupName) + } + // customized by user as override + if len(pp) > 1 { + name = plugin.Name(pp[1]) + } + + if kind == "" || execName == "" { + err = fmt.Errorf("invalid launch spec: %v", arg) + } + return +} + +// Options are tuning parameters for executing a task in context of +// a set of plugins that are started as required. +type Options struct { + // StartWait is how long to wait to make sure all plugins are up + StartWait types.Duration + // StopWait is how long to wait to make sure all plugins are shut down + StopWait types.Duration +} + +// Execute runs a unit of work with the specified list of plugins +// running. +func Execute(plugins func() discovery.Plugins, + pluginManager *manager.Manager, + starts func() ([]StartPlugin, error), + do scope.Work, options Options) error { + + pluginsToStart, err := starts() + if err != nil { + return err + } + + // first start up the plugins + for _, plugin := range pluginsToStart { + execName, kind, name, err := plugin.Parse() + if err != nil { + return err + } + + err = pluginManager.Launch(execName, kind, name, nil) + if err != nil { + log.Warn("failed to launch", "exec", execName, "kind", kind, "name", name) + } + return err + } + + defer func() { + <-time.After(options.StopWait.Duration()) + pluginManager.TerminateAll() + pluginManager.WaitForAllShutdown() + pluginManager.Stop() + }() + + pluginManager.WaitStarting() + <-time.After(options.StartWait.Duration()) + + log.Debug("Executing work in scope", "V", debugV) + err = do(scope.Scope{ + Plugins: plugins, // Full access. TODO -- scope this + }) + if err != nil { + log.Error("error processing in scope", "err", err) + } + return err +} diff --git a/pkg/run/scope/scope.go b/pkg/run/scope/scope.go new file mode 100644 index 000000000..35f18f301 --- /dev/null +++ b/pkg/run/scope/scope.go @@ -0,0 +1,26 @@ +package scope + +import ( + "github.com/docker/infrakit/pkg/discovery" +) + +// Scope provides an environment in which the necessary plugins are available +// for doing a unit of work. The scope can be local or remote, namespaced, +// depending on implementation. The first implementation is to simply run +// a set of steps locally on a set of required plugins. Because the scope +// provides the plugin lookup, it can control what plugins are available. +// This is good for programmatically control access of what a piece of code +// can interact with the system. +// Scope is named scope instead of 'context' because it's much heavier weight +// and involves lots of calls across process boundaries, yet it provides +// lookup and scoping of services based on some business logical and locality +// of code. +type Scope struct { + + // Plugins returns the plugin lookup + Plugins func() discovery.Plugins +} + +// Work is a unit of work that is executed in the scope of the plugins +// running. When work completes, the plugins are shutdown. +type Work func(Scope) error diff --git a/pkg/run/template/std.go b/pkg/run/template/std.go index a1875b482..0d832686a 100644 --- a/pkg/run/template/std.go +++ b/pkg/run/template/std.go @@ -21,16 +21,27 @@ func StdFunctions(engine *template.Template, plugins func() discovery.Plugins) * }, // This is an override of the existing Var function { - Name: "var2", + Name: "var", Func: func(name string, optional ...interface{}) (interface{}, error) { - // returns nil if it's a read and unresolved - // or if it's a write, returns a void value that is not nil (an empty string) - v := engine.Var(name, optional...) + if len(optional) > 0 { + return engine.Var(name, optional...), nil + } + + v := engine.Ref(name) if v == nil { // If not resolved, try to interpret the path as a path for metadata... - return metadata_template.MetadataFunc(plugins)(name, optional...) + m, err := metadata_template.MetadataFunc(plugins)(name, optional...) + if err != nil { + return nil, err + } + v = m } + + if v == nil && engine.Options().MultiPass { + return engine.DeferVar(name), nil + } + return v, nil }, }, diff --git a/pkg/template/template.go b/pkg/template/template.go index e3de8f244..00836646b 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -197,7 +197,7 @@ func (t *Template) RemoveFunc(name ...string) *Template { return t } -// Ref returns the value keyed by name in the context of this template. See 'ref' template function. +// Ref returns the value keyed by name in the context of this template. func (t *Template) Ref(name string) interface{} { if found, has := t.globals[name]; has { return found @@ -241,6 +241,24 @@ func (t *Template) Global(name string, value interface{}) *Template { return t } +// DeferVar returns a template expression for a var that isn't resolved at this iteration +func (t *Template) DeferVar(name string) string { + dl := t.options.DelimLeft + dr := t.options.DelimRight + if dl == "" { + dl = "{{" + } + if dr == "" { + dr = "}}" + } + // Handling of optional parameter isn't possible here because by now the + // template engine has already done the variable expansions and we have full values. + // Cases like {{ var "my-var" $defaultValue }} will render to {{ var `my-var` }}. + // Also this will not work in the case of pipeline - like {{ $x | var "my-var" }} -- + // which will just render to {{ var `my-var` }} + return fmt.Sprintf("%s var `%s` %s", dl, name, dr) +} + // Var implements the var function. It's a combination of global and ref // Note that the behavior of the var function depends on whether the template is used // in multiple passes where some var cannot be resolved to values in the first pass. @@ -256,21 +274,7 @@ func (t *Template) Var(name string, optional ...interface{}) interface{} { if base != nil { return base } - - dl := t.options.DelimLeft - dr := t.options.DelimRight - if dl == "" { - dl = "{{" - } - if dr == "" { - dr = "}}" - } - // Handling of optional parameter isn't possible here because by now the - // template engine has already done the variable expansions and we have full values. - // Cases like {{ var "my-var" $defaultValue }} will render to {{ var `my-var` }}. - // Also this will not work in the case of pipeline - like {{ $x | var "my-var" }} -- - // which will just render to {{ var `my-var` }} - return fmt.Sprintf("%s var `%s` %s", dl, name, dr) + return t.DeferVar(name) } // var implements the var function. It's a combination of global and ref From 2658836578199ba048a96a94b2f69d89aa72316f Mon Sep 17 00:00:00 2001 From: David Chung Date: Sun, 22 Oct 2017 05:09:58 -0700 Subject: [PATCH 02/11] simple auto-start plugins based on parsed specs; docs Signed-off-by: David Chung --- cmd/infrakit/util/init/init.go | 137 +++++++++++++++++------- docs/cmd/infrakit/util/init/README.md | 44 ++++++++ docs/cmd/infrakit/util/init/common.ikt | 24 +++++ docs/cmd/infrakit/util/init/groups.json | 49 +++++++++ docs/cmd/infrakit/util/init/init.sh | 5 + docs/cmd/infrakit/util/init/vars.json | 19 ++++ pkg/core/addressable.go | 6 ++ pkg/core/addressable_test.go | 5 + pkg/run/scope/local/local.go | 9 +- 9 files changed, 256 insertions(+), 42 deletions(-) create mode 100644 docs/cmd/infrakit/util/init/README.md create mode 100644 docs/cmd/infrakit/util/init/common.ikt create mode 100644 docs/cmd/infrakit/util/init/groups.json create mode 100644 docs/cmd/infrakit/util/init/init.sh create mode 100644 docs/cmd/infrakit/util/init/vars.json diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index 12d83d041..f1ebf3669 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -6,25 +6,28 @@ import ( "time" "github.com/docker/infrakit/pkg/cli" + "github.com/docker/infrakit/pkg/core" "github.com/docker/infrakit/pkg/discovery" "github.com/docker/infrakit/pkg/launch" logutil "github.com/docker/infrakit/pkg/log" "github.com/docker/infrakit/pkg/plugin" group_types "github.com/docker/infrakit/pkg/plugin/group/types" flavor_rpc "github.com/docker/infrakit/pkg/rpc/flavor" + metadata_rpc "github.com/docker/infrakit/pkg/rpc/metadata" "github.com/docker/infrakit/pkg/run/manager" "github.com/docker/infrakit/pkg/run/scope" "github.com/docker/infrakit/pkg/run/scope/local" "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/spi/metadata" "github.com/docker/infrakit/pkg/types" "github.com/spf13/cobra" ) var log = logutil.New("module", "util/init") -func getPluginManager(plugins func() discovery.Plugins, services *cli.Services, - configURL string, starts []string) (*manager.Manager, error) { +func getPluginManager(plugins func() discovery.Plugins, + services *cli.Services, configURL string) (*manager.Manager, error) { parsedRules := []launch.Rule{} @@ -61,10 +64,13 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { sequence := cmd.Flags().Uint("sequence", 0, "Sequence in the group") configURL := cmd.Flags().String("config-url", "", "URL for the startup configs") - starts := cmd.Flags().StringSlice("start", []string{}, "start spec for plugin just like infrakit plugin start") debug := cmd.Flags().Bool("debug", false, "True to debug with lots of traces") - waitDuration := cmd.Flags().String("wait", "3s", "Wait for plugins to be ready") + waitDuration := cmd.Flags().String("wait", "1s", "Wait for plugins to be ready") + + starts := cmd.Flags().StringSlice("start", []string{}, "start spec for plugin just like infrakit plugin start") + + persist := cmd.Flags().Bool("persist", false, "True to persist any vars into backend") cmd.RunE = func(c *cobra.Command, args []string) error { @@ -84,51 +90,82 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { wait := types.MustParseDuration(*waitDuration) - pluginManager, err := getPluginManager(plugins, services, *configURL, *starts) + pluginManager, err := getPluginManager(plugins, services, *configURL) if err != nil { return err } - buildInit := func(scope scope.Scope) error { - - input, err := services.ReadFromStdinIfElse( - func() bool { return args[0] == "-" }, - func() (string, error) { return services.ProcessTemplate(args[0]) }, - services.ToJSON, - ) + log.Info("Starting up base plugins") + basePlugins := []string{"vars"} + if *persist { + basePlugins = []string{"vars:vars-stateless", "manager:vars", "group:group-stateless"} // manager aliased to vars + } + for _, base := range basePlugins { + execName, kind, name, _ := local.StartPlugin(base).Parse() + err := pluginManager.Launch(execName, kind, name, nil) if err != nil { - log.Error("processing input", "err", err) + log.Error("cannot start base plugin", "spec", base) return err } + } + pluginManager.WaitStarting() + <-time.After(wait.Duration()) + + log.Info("Parsing the input groups.json as template") + input, err := services.ReadFromStdinIfElse( + func() bool { return args[0] == "-" }, + func() (string, error) { return services.ProcessTemplate(args[0]) }, + services.ToJSON, + ) + if err != nil { + log.Error("processing input", "err", err) + return err + } - // TODO - update the schema -- this matches the current Plugin/Properties schema - type spec struct { - Plugin plugin.Name - Properties struct { - ID group.ID - Properties group_types.Spec - } + // TODO - update the schema soon. This is the Plugin/Properties schema + type spec struct { + Plugin plugin.Name + Properties struct { + ID group.ID + Properties group_types.Spec } + } - specs := []spec{} - err = types.AnyString(input).Decode(&specs) - if err != nil { - return err - } + specs := []spec{} + err = types.AnyString(input).Decode(&specs) + if err != nil { + return err + } - var groupSpec *group_types.Spec - for _, s := range specs { - if string(s.Properties.ID) == *groupID { - copy := s.Properties.Properties - groupSpec = © - break - } + var groupSpec *group_types.Spec + for _, s := range specs { + if string(s.Properties.ID) == *groupID { + copy := s.Properties.Properties + groupSpec = © + break } + } + + if groupSpec == nil { + return fmt.Errorf("no such group: %v", *groupID) + } - if groupSpec == nil { - return fmt.Errorf("no such group: %v", *groupID) + // Found group spec + log.Info("Found group spec", "group", *groupID) + + // Now load the plugins + pluginsToStart := func() (targets []local.StartPlugin, err error) { + for _, start := range *starts { + targets = append(targets, local.StartPlugin(start)) } + // TODO -- get the dependencies too + targets = append(targets, local.FromAddressable(core.AddressableFromPluginName(groupSpec.Flavor.Plugin))) + return + } + + buildInit := func(scope scope.Scope) error { + // Get the flavor properties and use that to call the prepare of the Flavor to generate the init endpoint, err := scope.Plugins().Find(groupSpec.Flavor.Plugin) if err != nil { @@ -175,18 +212,38 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { return err } + if *persist { + vars := plugin.Name("vars") + log.Info("Persisting data into the backend") + endpoint, err := scope.Plugins().Find(vars) + if err != nil { + return err + } + m, err := metadata_rpc.NewClient(vars, endpoint.Address) + if err != nil { + return err + } + u, is := m.(metadata.Updatable) + if !is { + return fmt.Errorf("not updatable") + } + _, proposed, cas, err := u.Changes([]metadata.Change{}) + if err != nil { + return err + } + err = u.Commit(proposed, cas) + if err != nil { + return err + } + } + fmt.Print(applied) return nil } return local.Execute(plugins, pluginManager, - func() (targets []local.StartPlugin, err error) { - for _, start := range *starts { - targets = append(targets, local.StartPlugin(start)) - } - return - }, + pluginsToStart, buildInit, local.Options{ StartWait: wait, diff --git a/docs/cmd/infrakit/util/init/README.md b/docs/cmd/infrakit/util/init/README.md new file mode 100644 index 000000000..d00d402f4 --- /dev/null +++ b/docs/cmd/infrakit/util/init/README.md @@ -0,0 +1,44 @@ +init +==== + +The `init` command takes a spec (`groups.json`) and starts +the flavor plugin and generates the init script of the boot +node as though it's provisioned by the group and instance plugins. +This is used primarily as a way too bootstrap the first node of a +cluster. + +The `init` command also implicitly starts a `vars` plugin for storing +your varaibles so that through successive evaluations of templates, your +configuration information is always accessible. + +This example here uses the `vars.json`, `groups.json`, `common.ikt`, and `init.sh` +in this directory. + +To run + +```shell +$ INFRAKIT_VARS_TEMPLATE=file://$(pwd)/vars.json infrakit util init --group-id managers groups.json --var vars/config/root=file://$(pwd) +#!/bin/bash + +echo "This is the init script" +echo "The config root is file:///Users/davidchung/project5/src/github.com/docker/infrakit/docs/cmd/infrakit/util/init" +echo "The cluster size is 5" +``` + +The output will be the shell script that the first node of the group `managers` should +execute. You can pipe this to `sh` and the node that runs this script will become +the first node of the cluster. Note that there are other requirements on the first node +in terms of its configuration and labels (must have a label `infrakit.config_sha=bootstrap`) +such that it's not possible to run this on your mac and expect it to be the first node of +a cluster in AWS. This is meant to be for bootstrapping in the cloud environment where the +cluster will be. + +Note that we have the `init.sh` like so: + +``` +#!/bin/bash + +echo "This is the init script" +echo "The config root is {{ var `vars/config/root` }}" +echo "The cluster size is {{ var `vars/cluster/size` }}" +``` diff --git a/docs/cmd/infrakit/util/init/common.ikt b/docs/cmd/infrakit/util/init/common.ikt new file mode 100644 index 000000000..8375af652 --- /dev/null +++ b/docs/cmd/infrakit/util/init/common.ikt @@ -0,0 +1,24 @@ +{{/* Var variables */}} + +{{ var `/cluster/swarm/join/ip` `172.31.16.101` }} +{{ var `/cluster/swarm/manager/ips` (tuple `172.31.16.101` `172.31.16.102` `172.31.16.103`) }} +{{ if empty (var `/local/docker/engine/labels`) }}{{ var `/local/docker/engine/labels` (jsonDecode `[]`) }}{{end}} + +{{/* +Integration with the metadata plugin here. +See infrakit.sh when booting up, we store the variables from cloudformation +in the vars metadata plugin so that the user-entered values like cluster size can be +queried later even after the bootstrapping (cloudformation) completes. +*/}} +{{ if metadata `vars` }} +{{ var `/cluster/provider` ( metadata `vars/cluster/provider` ) }} +{{ var `/cluster/name` ( metadata `vars/cluster/name` )}} +{{ var `/cluster/size` ( metadata `vars/cluster/size` )}} +{{ var `/infrakit/config/root` ( metadata `vars/infrakit/config/root` )}} +{{ var `/infrakit/metadata/configURL` ( metadata `vars/infrakit/metadata/configURL` ) }} +{{ var `/infrakit/docker/image` ( metadata `vars/infrakit/docker/image` )}} +{{ var `/provider/image/hasDocker` ( metadata `vars/provider/image/hasDocker` )}} +{{ end }} + +{{ var `/local/install/docker` (var `/provider/image/hasDocker` | default `no` | eq `no` ) }} +{{ var `/local/docker/user` `ubuntu` }} diff --git a/docs/cmd/infrakit/util/init/groups.json b/docs/cmd/infrakit/util/init/groups.json new file mode 100644 index 000000000..735423710 --- /dev/null +++ b/docs/cmd/infrakit/util/init/groups.json @@ -0,0 +1,49 @@ +{{ source "common.ikt" }}{{/* source some variables */}} + +{{/* Local variables */}} +{{ $swarmLeaderIP := var `vars/cluster/swarm/joinIP` }} +{{ $managerIPs := var `vars/cluster/swarm/managerIPs` }} +{{ $workerSize := sub (var `vars/cluster/size`) (len $managerIPs) }} +{{ $managerInit := cat (var `vars/config/root`) `/init.sh` | nospace }} + +{{ $dockerEngine := `unix:///var/run/docker.sock` }} {{/* for this flavor to connect to */}} +[ + { + "Plugin": "group", + "Properties": { + "ID": "managers", + "Properties": { + "Allocation": { + "LogicalIDs": {{ $managerIPs | jsonEncode }} + }, + "Flavor": { + "Plugin": "swarm/manager", + "Properties": { + "Attachments" : { + "{{index $managerIPs 0}}" : [ + { "ID":"{{index $managerIPs 0}}", "Type":"ebs" } + ], + "{{index $managerIPs 1}}" : [ + { "ID":"{{index $managerIPs 1}}", "Type":"ebs" } + ], + "{{index $managerIPs 2}}" : [ + { "ID":"{{index $managerIPs 2}}", "Type":"ebs" } + ] + }, + "InitScriptTemplateURL": "{{ $managerInit }}", + "SwarmJoinIP": "{{ $swarmLeaderIP }}", + "Docker" : { + "Host" : "{{ $dockerEngine }}" + } + } + }, + "Instance": { + "Plugin" : "simulator/compute", + "Properties" : { + "CustomNotes" : "This is custom for manager" + } + } + } + } + } +] diff --git a/docs/cmd/infrakit/util/init/init.sh b/docs/cmd/infrakit/util/init/init.sh new file mode 100644 index 000000000..218063835 --- /dev/null +++ b/docs/cmd/infrakit/util/init/init.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "This is the init script" +echo "The config root is {{ var `vars/config/root` }}" +echo "The cluster size is {{ var `vars/cluster/size` }}" diff --git a/docs/cmd/infrakit/util/init/vars.json b/docs/cmd/infrakit/util/init/vars.json new file mode 100644 index 000000000..3957029c4 --- /dev/null +++ b/docs/cmd/infrakit/util/init/vars.json @@ -0,0 +1,19 @@ +{{/* +The vars.json is itself a template. You can embed template functions +which will be evaluated when the engine renders itself to create the +JSON used by the vars plugin. +*/}} +{{ var `cluster/user/name` `user` }} +{{ var `zones/east/cidr` `10.20.100.100/24` }} +{{ var `zones/west/cidr` `10.20.100.200/24` }} +{ + "cluster" : { + "size" : 5, + "name" : "test", + "swarm" : { + "joinIP" : "10.20.100.101", + "managerIPs" : [ "10.20.100.101", "10.20.100.102", "10.20.100.103" ] + } + }, + "shell" : {{ env `SHELL` }} +} diff --git a/pkg/core/addressable.go b/pkg/core/addressable.go index 3aea67284..882adebb5 100644 --- a/pkg/core/addressable.go +++ b/pkg/core/addressable.go @@ -49,6 +49,12 @@ func NewAddressableFromMetadata(kind string, metadata types.Metadata) Addressabl return NewAddressable(kind, plugin.Name(metadata.Name), instance) } +// NewAddressable returns a generic addressable object from just the plugin name. +// The kind is assume to be the same as the lookup. +func AddressableFromPluginName(pn plugin.Name) Addressable { + return NewAddressable(pn.Lookup(), pn, "") +} + // NewAddressable returns a generic addressable object func NewAddressable(kind string, pn plugin.Name, instance string) Addressable { n := string(pn) diff --git a/pkg/core/addressable_test.go b/pkg/core/addressable_test.go index 7bff06fd9..f0aa9d39a 100644 --- a/pkg/core/addressable_test.go +++ b/pkg/core/addressable_test.go @@ -53,6 +53,11 @@ metadata: require.Equal(t, "group", c.Kind()) require.Equal(t, "group-stateless/mygroup", string(c.Plugin())) require.Equal(t, "mygroup", c.Instance()) + + c = AddressableFromPluginName(plugin.Name("swarm/manager")) + require.Equal(t, "swarm", c.Kind()) + require.Equal(t, "swarm/manager", string(c.Plugin())) + require.Equal(t, "swarm", c.Plugin().Lookup()) } func TestDerivePluginNames(t *testing.T) { diff --git a/pkg/run/scope/local/local.go b/pkg/run/scope/local/local.go index b15fb5eb6..a0dd2ef97 100644 --- a/pkg/run/scope/local/local.go +++ b/pkg/run/scope/local/local.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/docker/infrakit/pkg/core" "github.com/docker/infrakit/pkg/discovery" logutil "github.com/docker/infrakit/pkg/log" "github.com/docker/infrakit/pkg/plugin" @@ -25,6 +26,11 @@ var ( // The format is kind[:{plugin_name}][={os|inproc}] type StartPlugin string +// FromAddressable returns a StartPlugin encoded string +func FromAddressable(addr core.Addressable) StartPlugin { + return StartPlugin(fmt.Sprintf("%v:%v", addr.Kind(), addr.Plugin().Lookup())) +} + // Parse parses the specification into parts that the manager can use to launch plugins func (arg StartPlugin) Parse() (execName string, kind string, name plugin.Name, err error) { p := strings.Split(string(arg), "=") @@ -83,12 +89,11 @@ func Execute(plugins func() discovery.Plugins, if err != nil { return err } - err = pluginManager.Launch(execName, kind, name, nil) if err != nil { log.Warn("failed to launch", "exec", execName, "kind", kind, "name", name) + return err } - return err } defer func() { From 2a0611eedc8565ca5573f6f01db375c981f08e69 Mon Sep 17 00:00:00 2001 From: David Chung Date: Sun, 22 Oct 2017 05:56:00 -0700 Subject: [PATCH 03/11] added pesistence option Signed-off-by: David Chung --- cmd/infrakit/util/init/init.go | 5 +++-- pkg/manager/manager.go | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index f1ebf3669..d63c9316f 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -98,7 +98,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { log.Info("Starting up base plugins") basePlugins := []string{"vars"} if *persist { - basePlugins = []string{"vars:vars-stateless", "manager:vars", "group:group-stateless"} // manager aliased to vars + basePlugins = []string{"vars", "manager", "group"} // manager aliased to vars } for _, base := range basePlugins { execName, kind, name, _ := local.StartPlugin(base).Parse() @@ -213,7 +213,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { } if *persist { - vars := plugin.Name("vars") + vars := plugin.Name("group/vars") log.Info("Persisting data into the backend") endpoint, err := scope.Plugins().Find(vars) if err != nil { @@ -232,6 +232,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { return err } err = u.Commit(proposed, cas) + log.Info("Committed to vars", "err", err) if err != nil { return err } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 1ed5db005..5be5ef4c7 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -212,7 +212,7 @@ func (m *manager) Start() (<-chan struct{}, error) { // same leader node. err := m.loadMetadata() if err != nil { - log.Error("error loading metadata", "err", err) + log.Debug("error loading metadata", "err", err) } <-metadataRefresh @@ -385,7 +385,7 @@ func (m *manager) loadMetadata() (err error) { return nil } - log.Info("loading metadata and committing") + log.Debug("loading metadata and committing") var saved interface{} err = m.Options.MetadataStore.Load(&saved) @@ -400,7 +400,7 @@ func (m *manager) loadMetadata() (err error) { } if any == nil { - log.Info("no metadata stored") + log.Debug("no metadata stored") return } From 3f015ebb8835e81e2d6fbd1d9f206a8cfe7a1cc8 Mon Sep 17 00:00:00 2001 From: David Chung Date: Mon, 23 Oct 2017 03:25:56 -0700 Subject: [PATCH 04/11] added metadata flags; updated docs Signed-off-by: David Chung --- cmd/infrakit/util/init/init.go | 21 +++++ docs/cmd/infrakit/util/init/README.md | 109 ++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index d63c9316f..1d6d74cd6 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -3,6 +3,7 @@ package init import ( "fmt" "os" + "strings" "time" "github.com/docker/infrakit/pkg/cli" @@ -12,6 +13,7 @@ import ( logutil "github.com/docker/infrakit/pkg/log" "github.com/docker/infrakit/pkg/plugin" group_types "github.com/docker/infrakit/pkg/plugin/group/types" + metadata_template "github.com/docker/infrakit/pkg/plugin/metadata/template" flavor_rpc "github.com/docker/infrakit/pkg/rpc/flavor" metadata_rpc "github.com/docker/infrakit/pkg/rpc/metadata" "github.com/docker/infrakit/pkg/run/manager" @@ -71,6 +73,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { starts := cmd.Flags().StringSlice("start", []string{}, "start spec for plugin just like infrakit plugin start") persist := cmd.Flags().Bool("persist", false, "True to persist any vars into backend") + metadatas := cmd.Flags().StringSlice("metadata", []string{}, "key=value to set metadata") cmd.RunE = func(c *cobra.Command, args []string) error { @@ -111,6 +114,24 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { pluginManager.WaitStarting() <-time.After(wait.Duration()) + if len(*metadatas) > 0 { + log.Info("Setting metadata entries") + mfunc := metadata_template.MetadataFunc(plugins) + for _, md := range *metadatas { + // TODO -- this is not transactional.... we don't know + // the paths and there may be changes to multiple metadata + // plugins. For now we just process one by one. + kv := strings.Split(md, "=") + if len(kv) == 2 { + _, err := mfunc(kv[0], kv[1]) + if err != nil { + return err + } + log.Info("written metadata", "key", kv[0], "value", kv[1]) + } + } + } + log.Info("Parsing the input groups.json as template") input, err := services.ReadFromStdinIfElse( func() bool { return args[0] == "-" }, diff --git a/docs/cmd/infrakit/util/init/README.md b/docs/cmd/infrakit/util/init/README.md index d00d402f4..c426cf4ab 100644 --- a/docs/cmd/infrakit/util/init/README.md +++ b/docs/cmd/infrakit/util/init/README.md @@ -14,6 +14,45 @@ configuration information is always accessible. This example here uses the `vars.json`, `groups.json`, `common.ikt`, and `init.sh` in this directory. +### Operation + +To render the init script from the groups specification is complex and involves +multiple steps. This is because bootstrapping from a single node requires a mix +of user-provided data, as well as values that are available *after* some additional +future step (e.g. getting a cluster master to provide a join token -- which is not +available until the cluster is bootstrapping itself). So while the configuration specs +and scripts look like a set of point-in-time configurations, multiple evaluations +of these as templates are performed as more data become available. The sequence +of `init` works as follows: + + 1. The user provides the spec's URL (the `groups.json` URL) + 2. The CLI fetches this and evaluates it as a template. + + The engine uses the `INFRAKIT_VARS_TEMPLATE` env to determine a variables JSON to + initialize some variables. These can be overridden by appropriately named `--var` + and `--metdata` flags in the command line. + + The engine uses the `--var` to set parameters in memory for the scope of *this* + evaluation. These are ephemeral and can override the defaults set in above. + + The engine uses the `--metadata` to set parameters that will persist, as some + kind of cluster-state. These values are persistent and are written to the backend + based on your configuration (eg. swarm, etcd, or file). + 3. The CLI parses the spec, and locates the section for the group as specified by + the `group-id` flag. + 4. The CLI now starts the plugins specified in the spec. For example, the spec + here references the `swarm/managers` plugin, so the CLI starts up the `swarm` plugin. + 5. The CLI invokes the flavor plugin's `Prepare` method. + 6. The Flavor plugin performs the necessary work such as generating tokens, etc. and + renders a template. Depending on the plugin implementation, values that are not known + at this time may be deferred (as multipass = true -- see `swarm/flavor.go#31). + 7. The CLI renders the text blob from the `Init` field of the instance spec returned + by the `Prepare` method. This will apply the same set of `--var` as varaibles. + 8. The CLI prints out the rendered init script + 9. If `--persist` is set, the CLI will commit the current state of the vars plugin + (which was started in the beginning of this process). + +** This is pretty complicated but the details here are presented for documentation. +The end user only knows there are certain variables to set to bootstrap a cluster, +and the values are set via the `--var` and `--metadata` flags (e.g. the size of the cluster). + To run ```shell @@ -42,3 +81,73 @@ echo "This is the init script" echo "The config root is {{ var `vars/config/root` }}" echo "The cluster size is {{ var `vars/cluster/size` }}" ``` + +### Persist values via `--metadata` + +We don't always want every parameter used for templates to be stored as cluster state, +but there are times, some parameters need to persist from node to node and through time. +For those parameters, use `--metadata` followed by a `--persist` to ensure data is persisted +into the the backend that's configured. + +```shell +$INFRAKIT_VARS_TEMPLATE=file://$(pwd)/vars.json infrakit util init --group-id managers groups.json --metadata vars/config/root=file://$(pwd) --persist +``` + +This yields the same: + +``` +#!/bin/bash + +echo "This is the init script" +echo "The config root is file:///Users/davidchung/project5/src/github.com/docker/infrakit/docs/cmd/infrakit/util/init" +echo "The cluster size is 5" +``` + +However, the data has been snapshoted and persisted -- in this case as a file: + +```shell +$ export INFRAKIT_HOME=~/.infrakit +$ cat $INFRAKIT_HOME/configs/vars.vars +{ + "cluster": { + "name": "test", + "size": 5, + "swarm": { + "joinIP": "10.20.100.101", + "managerIPs": [ + "10.20.100.101", + "10.20.100.102", + "10.20.100.103" + ] + }, + "user": { + "name": "user" + } + }, + "config": { + "root": "file:///Users/davidchung/project5/src/github.com/docker/infrakit/docs/cmd/infrakit/util/init" + }, + "shell": "/bin/bash", + "zones": { + "east": { + "cidr": "10.20.100.100/24" + }, + "west": { + "cidr": "10.20.100.200/24" + } + } + } +``` + +Next time if `manager` is started it will automatically load this state. In case +of failover, the manager on another node will load this as well as the cluster spec +as part of failing over. So we have the cluster specs as well as user-provided +data fully available. + +### `--var` or `--metadata`? + +This is a matter of tasted as they serve different purposes. If a parameter is only +meant to be temporary, `--var` will avoid cluttering up the data store. However, +`--metadata` is easier to reason about across time and location because that data +is highly available. For simplicity, `--metadata` may be the way to start off developing +your custom templates. From 0506e98b68618c047157d31b4c335f8d2607a630 Mon Sep 17 00:00:00 2001 From: David Chung Date: Mon, 23 Oct 2017 03:34:33 -0700 Subject: [PATCH 05/11] doc fix Signed-off-by: David Chung --- docs/cmd/infrakit/util/init/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/cmd/infrakit/util/init/README.md b/docs/cmd/infrakit/util/init/README.md index c426cf4ab..4c725249d 100644 --- a/docs/cmd/infrakit/util/init/README.md +++ b/docs/cmd/infrakit/util/init/README.md @@ -146,8 +146,9 @@ data fully available. ### `--var` or `--metadata`? -This is a matter of tasted as they serve different purposes. If a parameter is only -meant to be temporary, `--var` will avoid cluttering up the data store. However, +This is a matter of taste and requirements, as they serve different purposes. +If a parameter is only meant to be temporary, `--var` will avoid cluttering up +the data store. However, `--metadata` is easier to reason about across time and location because that data is highly available. For simplicity, `--metadata` may be the way to start off developing your custom templates. From 1decf03eaced39dacde1f276f020387b2967b4a6 Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Oct 2017 09:08:43 -0700 Subject: [PATCH 06/11] auto-start plugings by analyzing the input Signed-off-by: David Chung --- cmd/infrakit/util/init/init.go | 36 +++- docs/cmd/infrakit/util/init/groups.json | 50 +++-- pkg/controller/enrollment/types/types.go | 11 +- pkg/controller/ingress/types/types.go | 6 +- pkg/plugin/flavor/combo/depends.go | 32 +++ pkg/plugin/flavor/combo/depends_test.go | 44 ++++ pkg/plugin/group/types/types.go | 54 ++++- pkg/run/depends/depends.go | 5 +- pkg/run/depends/depends_test.go | 59 ++++++ pkg/run/depends/runnable.go | 116 +++++++++++ pkg/run/depends/runnable_test.go | 252 +++++++++++++++++++++++ pkg/run/manager/manager.go | 1 + pkg/types/interface_spec.go | 19 +- 13 files changed, 652 insertions(+), 33 deletions(-) create mode 100644 pkg/plugin/flavor/combo/depends.go create mode 100644 pkg/plugin/flavor/combo/depends_test.go create mode 100644 pkg/run/depends/depends_test.go create mode 100644 pkg/run/depends/runnable.go create mode 100644 pkg/run/depends/runnable_test.go diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index 1d6d74cd6..6bf174ce3 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -16,9 +16,11 @@ import ( metadata_template "github.com/docker/infrakit/pkg/plugin/metadata/template" flavor_rpc "github.com/docker/infrakit/pkg/rpc/flavor" metadata_rpc "github.com/docker/infrakit/pkg/rpc/metadata" + "github.com/docker/infrakit/pkg/run/depends" "github.com/docker/infrakit/pkg/run/manager" "github.com/docker/infrakit/pkg/run/scope" "github.com/docker/infrakit/pkg/run/scope/local" + group_kind "github.com/docker/infrakit/pkg/run/v0/group" "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/spi/metadata" @@ -26,7 +28,26 @@ import ( "github.com/spf13/cobra" ) -var log = logutil.New("module", "util/init") +var log = logutil.New("module", "cmd/infrakit/util/init") + +func toSpec(gid group.ID, g group_types.Spec) (spec types.Spec, err error) { + any, e := types.AnyValue(g) + if e != nil { + err = e + return + } + spec = types.Spec{ + Kind: group_kind.Kind, + Version: group.InterfaceSpec.Encode(), + Metadata: types.Metadata{ + Identity: &types.Identity{ID: string(gid)}, + Name: plugin.NameFrom(group_kind.Kind, string(gid)).String(), + }, + Properties: any, + Options: nil, // TOOD -- the old format doesn't have this information. + } + return +} func getPluginManager(plugins func() discovery.Plugins, services *cli.Services, configURL string) (*manager.Manager, error) { @@ -176,12 +197,23 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { // Now load the plugins pluginsToStart := func() (targets []local.StartPlugin, err error) { + for _, start := range *starts { targets = append(targets, local.StartPlugin(start)) } - // TODO -- get the dependencies too targets = append(targets, local.FromAddressable(core.AddressableFromPluginName(groupSpec.Flavor.Plugin))) + + if spec, err := toSpec(group.ID(*groupID), *groupSpec); err == nil { + log.Debug("resolving", "groupID", *groupID, "spec", spec) + if other, err := depends.Resolve(spec, spec.Kind, nil); err == nil { + for _, r := range other { + targets = append(targets, local.FromAddressable(r)) + } + } + } + + log.Info("plugins to start", "targets", targets) return } diff --git a/docs/cmd/infrakit/util/init/groups.json b/docs/cmd/infrakit/util/init/groups.json index 735423710..e8755e380 100644 --- a/docs/cmd/infrakit/util/init/groups.json +++ b/docs/cmd/infrakit/util/init/groups.json @@ -17,25 +17,41 @@ "LogicalIDs": {{ $managerIPs | jsonEncode }} }, "Flavor": { - "Plugin": "swarm/manager", - "Properties": { - "Attachments" : { - "{{index $managerIPs 0}}" : [ - { "ID":"{{index $managerIPs 0}}", "Type":"ebs" } - ], - "{{index $managerIPs 1}}" : [ - { "ID":"{{index $managerIPs 1}}", "Type":"ebs" } - ], - "{{index $managerIPs 2}}" : [ - { "ID":"{{index $managerIPs 2}}", "Type":"ebs" } - ] + "Plugin" : "combo", + "Properties" : [ + { + "Plugin": "swarm/manager", + "Properties": { + "Attachments" : { + "{{index $managerIPs 0}}" : [ + { "ID":"{{index $managerIPs 0}}", "Type":"ebs" } + ], + "{{index $managerIPs 1}}" : [ + { "ID":"{{index $managerIPs 1}}", "Type":"ebs" } + ], + "{{index $managerIPs 2}}" : [ + { "ID":"{{index $managerIPs 2}}", "Type":"ebs" } + ] + }, + "InitScriptTemplateURL": "{{ $managerInit }}", + "SwarmJoinIP": "{{ $swarmLeaderIP }}", + "Docker" : { + "Host" : "{{ $dockerEngine }}" + } + } }, - "InitScriptTemplateURL": "{{ $managerInit }}", - "SwarmJoinIP": "{{ $swarmLeaderIP }}", - "Docker" : { - "Host" : "{{ $dockerEngine }}" + { + "Plugin": "vanilla", + "Properties": { + "Init" : [ + "sudo apt-get install -y something-else", + "./configure-something-else" + ] + } } - } + ] + + }, "Instance": { "Plugin" : "simulator/compute", diff --git a/pkg/controller/enrollment/types/types.go b/pkg/controller/enrollment/types/types.go index 3da0f9977..b796dab2a 100644 --- a/pkg/controller/enrollment/types/types.go +++ b/pkg/controller/enrollment/types/types.go @@ -21,7 +21,7 @@ func init() { } // ResolveDependencies returns a list of dependencies by parsing the opaque Properties blob. -func ResolveDependencies(spec types.Spec) ([]plugin.Name, error) { +func ResolveDependencies(spec types.Spec) (depends.Runnables, error) { if spec.Properties == nil { return nil, nil } @@ -32,7 +32,14 @@ func ResolveDependencies(spec types.Spec) ([]plugin.Name, error) { return nil, err } - return []plugin.Name{properties.Instance.Plugin}, nil + return depends.Runnables{ + depends.AsRunnable(types.Spec{ + Kind: properties.Instance.Plugin.Lookup(), + Metadata: types.Metadata{ + Name: properties.Instance.Plugin.String(), + }, + }), + }, nil } // ListSourceUnion is a union type of possible values: diff --git a/pkg/controller/ingress/types/types.go b/pkg/controller/ingress/types/types.go index fbdb99106..a1e360a45 100644 --- a/pkg/controller/ingress/types/types.go +++ b/pkg/controller/ingress/types/types.go @@ -18,7 +18,7 @@ func init() { } // ResolveDependencies returns a list of dependencies by parsing the opaque Properties blob. -func ResolveDependencies(spec types.Spec) ([]plugin.Name, error) { +func ResolveDependencies(spec types.Spec) (depends.Runnables, error) { if spec.Properties == nil { return nil, nil } @@ -29,9 +29,9 @@ func ResolveDependencies(spec types.Spec) ([]plugin.Name, error) { return nil, err } - out := []plugin.Name{} + out := depends.Runnables{} for _, p := range properties { - out = append(out, p.L4Plugin) + out = append(out, depends.RunnableFrom(p.L4Plugin)) } return out, nil } diff --git a/pkg/plugin/flavor/combo/depends.go b/pkg/plugin/flavor/combo/depends.go new file mode 100644 index 000000000..f65e4adfd --- /dev/null +++ b/pkg/plugin/flavor/combo/depends.go @@ -0,0 +1,32 @@ +package combo + +import ( + "github.com/docker/infrakit/pkg/run/depends" + "github.com/docker/infrakit/pkg/spi/flavor" + "github.com/docker/infrakit/pkg/types" +) + +func init() { + depends.Register("combo", types.InterfaceSpec(flavor.InterfaceSpec), ResolveDependencies) +} + +// ResolveDependencies returns a list of dependencies by parsing the opaque Properties blob. +// Do not include self -- only the children / dependent components. +func ResolveDependencies(spec types.Spec) (depends.Runnables, error) { + if spec.Properties == nil { + return nil, nil + } + + flavorSpecs := Spec{} + err := spec.Properties.Decode(&flavorSpecs) + if err != nil { + return nil, err + } + + runnables := depends.Runnables{} + for _, flavorSpec := range flavorSpecs { + included := depends.RunnableFrom(flavorSpec.Plugin) + runnables = append(runnables, included) + } + return runnables, nil +} diff --git a/pkg/plugin/flavor/combo/depends_test.go b/pkg/plugin/flavor/combo/depends_test.go new file mode 100644 index 000000000..4c47d003d --- /dev/null +++ b/pkg/plugin/flavor/combo/depends_test.go @@ -0,0 +1,44 @@ +package combo + +import ( + "testing" + + "github.com/docker/infrakit/pkg/plugin" + "github.com/docker/infrakit/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestDependencies(t *testing.T) { + + spec := types.Spec{ + Kind: "combo", + Metadata: types.Metadata{ + Name: "managers", + }, + Properties: types.AnyValueMust( + Spec{ + { + Plugin: plugin.Name("swarm/manager"), + Properties: types.AnyValueMust( + map[string]interface{}{ + "docker": "unix:///var/run/docker.sock", + }, + ), + }, + { + Plugin: plugin.Name("kubernetes/manager"), + Properties: types.AnyValueMust( + map[string]interface{}{ + "addOns": "weave", + }, + ), + }, + }, + ), + } + + runnables, err := ResolveDependencies(spec) + require.NoError(t, err) + require.Equal(t, "swarm", runnables[0].Plugin().Lookup()) + require.Equal(t, "kubernetes", runnables[1].Plugin().Lookup()) +} diff --git a/pkg/plugin/group/types/types.go b/pkg/plugin/group/types/types.go index 68a33e741..3177b68fe 100644 --- a/pkg/plugin/group/types/types.go +++ b/pkg/plugin/group/types/types.go @@ -19,18 +19,66 @@ func init() { } // ResolveDependencies returns a list of dependencies by parsing the opaque Properties blob. -func ResolveDependencies(spec types.Spec) ([]plugin.Name, error) { +func ResolveDependencies(spec types.Spec) (depends.Runnables, error) { + if spec.Properties == nil { return nil, nil } - groupSpec := Spec{} + // This extends on the group plugin spec to add optional Options section + type t struct { + Instance struct { + Plugin plugin.Name + Properties *types.Any + Options *types.Any + } + Flavor struct { + Plugin plugin.Name + Properties *types.Any + Options *types.Any + } + } + + groupSpec := t{} err := spec.Properties.Decode(&groupSpec) if err != nil { return nil, err } - return []plugin.Name{groupSpec.Instance.Plugin, groupSpec.Flavor.Plugin}, nil + instancePlugin := types.Spec{ + Kind: groupSpec.Instance.Plugin.Lookup(), + Metadata: types.Metadata{ + Name: groupSpec.Instance.Plugin.String(), + }, + Properties: groupSpec.Instance.Properties, + Options: groupSpec.Instance.Options, + } + + flavorPlugin := types.Spec{ + Kind: groupSpec.Flavor.Plugin.Lookup(), + Metadata: types.Metadata{ + Name: groupSpec.Flavor.Plugin.String(), + }, + Properties: groupSpec.Flavor.Properties, + Options: groupSpec.Flavor.Options, + } + + all := depends.Runnables{ + depends.AsRunnable(instancePlugin), + depends.AsRunnable(flavorPlugin), + } + + // For any instance / flavor plugins nested: + + nestedInstances, err := depends.Resolve(instancePlugin, instancePlugin.Kind, nil) + if err == nil { + all = append(all, nestedInstances...) + } + nestedFlavors, err := depends.Resolve(flavorPlugin, flavorPlugin.Kind, nil) + if err == nil { + all = append(all, nestedFlavors...) + } + return all, nil } // Spec is the configuration schema for the plugin, provided in group.Spec.Properties diff --git a/pkg/run/depends/depends.go b/pkg/run/depends/depends.go index 48b1c0de7..6af961781 100644 --- a/pkg/run/depends/depends.go +++ b/pkg/run/depends/depends.go @@ -5,14 +5,13 @@ import ( "sync" logutil "github.com/docker/infrakit/pkg/log" - "github.com/docker/infrakit/pkg/plugin" "github.com/docker/infrakit/pkg/types" ) var log = logutil.New("module", "run/depends") // ParseDependsFunc returns a list of dependencies of this spec. -type ParseDependsFunc func(types.Spec) ([]plugin.Name, error) +type ParseDependsFunc func(types.Spec) (Runnables, error) var ( parsers = map[string]map[types.InterfaceSpec]ParseDependsFunc{} @@ -38,7 +37,7 @@ func Register(key string, interfaceSpec types.InterfaceSpec, f ParseDependsFunc) // Resolve returns the dependencies listed in the spec as well as inside the properties. // InterfaceSpec is optional. If nil, the first match by key (kind) is used. If nothing is registered, returns nil // and no error. Error is returned for exceptions (eg. parsing, etc.) -func Resolve(spec types.Spec, key string, interfaceSpec *types.InterfaceSpec) ([]plugin.Name, error) { +func Resolve(spec types.Spec, key string, interfaceSpec *types.InterfaceSpec) (Runnables, error) { lock.RLock() defer lock.RUnlock() diff --git a/pkg/run/depends/depends_test.go b/pkg/run/depends/depends_test.go new file mode 100644 index 000000000..e183bf6d8 --- /dev/null +++ b/pkg/run/depends/depends_test.go @@ -0,0 +1,59 @@ +package depends + +import ( + "testing" + + "github.com/docker/infrakit/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestDepends(t *testing.T) { + + v := types.DecodeInterfaceSpec("Test/0.1") + Register("test", v, func(spec types.Spec) (Runnables, error) { + return Runnables{ + AsRunnable(mustSpec(types.SpecFromString(` +kind: simulator/compute +version: Instance/0.1 +metadata: + name: us-east1 +options: + poll: 10 +`))), + AsRunnable(mustSpec(types.SpecFromString(` +kind: swarm/manager +version: Flavor/0.1 +metadata: + name: swarm +options: + docker: /var/run/docker.sock +`))), + }, nil + }) + + found, err := Resolve(mustSpec(types.SpecFromString(``)), "test", &v) + require.NoError(t, err) + // in this case, the resolver always returns 2 + require.Equal(t, Runnables{ + AsRunnable(mustSpec(types.SpecFromString(` +kind: simulator/compute +version: Instance/0.1 +metadata: + name: us-east1 +options: + poll: 10 +`))), + AsRunnable(mustSpec(types.SpecFromString(` +kind: swarm/manager +version: Flavor/0.1 +metadata: + name: swarm +options: + docker: /var/run/docker.sock +`))), + }, found) + + found, err = Resolve(mustSpec(types.SpecFromString(``)), "nope", &v) + require.NoError(t, err) + require.Equal(t, 0, len(found)) +} diff --git a/pkg/run/depends/runnable.go b/pkg/run/depends/runnable.go new file mode 100644 index 000000000..da94e9d5f --- /dev/null +++ b/pkg/run/depends/runnable.go @@ -0,0 +1,116 @@ +package depends + +import ( + "fmt" + "sort" + + "github.com/docker/infrakit/pkg/core" + "github.com/docker/infrakit/pkg/plugin" + "github.com/docker/infrakit/pkg/types" +) + +// Runnable models an addressable object that can also be started. +type Runnable interface { + core.Addressable + // Options returns the options needed to start the plugin + Options() *types.Any + // Dependents return all the plugins this runnable depends on + Dependents() (Runnables, error) +} + +// Runnables represent a collection of Runnables +type Runnables []Runnable + +// RunnableFrom creates a runnable from input name. This is a simplification +// for cases where only a plugin name is used to reference another plugin. +func RunnableFrom(name plugin.Name) Runnable { + kind := name.Lookup() + return specQuery{ + Addressable: core.NewAddressable(kind, name, ""), + spec: types.Spec{ + Kind: kind, + Metadata: types.Metadata{ + Name: string(name), + }, + }, + } +} + +// AsRunnable returns the Runnable from a spec. +func AsRunnable(spec types.Spec) Runnable { + return &specQuery{ + Addressable: core.AsAddressable(spec), + spec: spec, + } +} + +type specQuery struct { + core.Addressable + spec types.Spec +} + +// Options returns the options +func (ps specQuery) Options() *types.Any { + return ps.spec.Options +} + +// Dependents returns the plugins depended on by this unit +func (ps specQuery) Dependents() (Runnables, error) { + + var interfaceSpec *types.InterfaceSpec + if ps.spec.Version != "" { + decoded := types.DecodeInterfaceSpec(ps.spec.Version) + interfaceSpec = &decoded + } + dependentPlugins, err := Resolve(ps.spec, ps.Kind(), interfaceSpec) + if err != nil { + return nil, err + } + log.Debug("dependentPlugins", "depends", dependentPlugins, "spec", ps.spec, "kind", ps.Kind(), "intf", interfaceSpec) + + // join this with the dependencies already in the spec + out := Runnables{} + out = append(out, dependentPlugins...) + + for _, d := range ps.spec.Depends { + out = append(out, AsRunnable(types.Spec{Kind: d.Kind, Metadata: types.Metadata{Name: d.Name}})) + } + + log.Debug("dependents", "specQuery", ps, "result", out) + return out, nil +} + +// RunnablesFrom returns the Runnables from given slice of specs +func RunnablesFrom(specs []types.Spec) (Runnables, error) { + + key := func(addr core.Addressable) string { + return fmt.Sprintf("%v::%v", addr.Kind(), addr.Plugin().Lookup()) + } + + keys := []string{} + // keyed by kind and the specQuery + all := map[string]Runnable{} + for _, s := range specs { + + q := AsRunnable(s) + all[key(q)] = q + keys = append(keys, key(q)) + + deps, err := q.Dependents() + if err != nil { + return nil, err + } + + for _, d := range deps { + all[key(d)] = d + keys = append(keys, key(d)) + } + } + + sort.Strings(keys) + out := Runnables{} + for _, k := range keys { + out = append(out, all[k]) + } + return out, nil +} diff --git a/pkg/run/depends/runnable_test.go b/pkg/run/depends/runnable_test.go new file mode 100644 index 000000000..62aa25bec --- /dev/null +++ b/pkg/run/depends/runnable_test.go @@ -0,0 +1,252 @@ +package depends + +import ( + "testing" + + "github.com/docker/infrakit/pkg/plugin" + "github.com/docker/infrakit/pkg/types" + "github.com/stretchr/testify/require" +) + +func mustSpec(s types.Spec, err error) types.Spec { + if err != nil { + panic(err) + } + return s +} + +func mustSpecs(s []types.Spec, err error) []types.Spec { + if err != nil { + panic(err) + } + return s +} + +func TestRunnable(t *testing.T) { + v := types.DecodeInterfaceSpec("Test/0.1") + Register("TestRunnable", v, func(spec types.Spec) (Runnables, error) { + return Runnables{AsRunnable(spec)}, nil + }) + + runnable := AsRunnable(mustSpec(types.SpecFromString(` +kind: group +metadata: + name: workers +properties: + max: 100 + min: 10 +options: + poll: 10 +`))) + + require.Equal(t, "group", runnable.Kind()) + require.Equal(t, plugin.Name("group/workers"), runnable.Plugin()) + require.Equal(t, "workers", runnable.Instance()) + options := map[string]int{} + require.NoError(t, runnable.Options().Decode(&options)) + require.Equal(t, map[string]int{"poll": 10}, options) + + deps, err := runnable.Dependents() + require.NoError(t, err) + require.Equal(t, Runnables{}, deps) +} + +func TestRunnableWithDepends(t *testing.T) { + v := types.DecodeInterfaceSpec("group/0.1") + Register("group", v, func(spec types.Spec) (Runnables, error) { + // This just echos back whatever comes in + return Runnables{AsRunnable(spec)}, nil + }) + + runnable := AsRunnable(mustSpec(types.SpecFromString(` +kind: group +version: group/0.1 +metadata: + name: workers +properties: + max: 100 + min: 10 +options: + poll: 10 +`))) + + require.Equal(t, "group", runnable.Kind()) + require.Equal(t, plugin.Name("group/workers"), runnable.Plugin()) + require.Equal(t, "workers", runnable.Instance()) + options := map[string]int{} + require.NoError(t, runnable.Options().Decode(&options)) + require.Equal(t, map[string]int{"poll": 10}, options) + + deps, err := runnable.Dependents() + require.NoError(t, err) + require.Equal(t, Runnables{runnable}, deps) +} + +func TestRunnablesFromSpec(t *testing.T) { + + Register("group", types.InterfaceSpec{Name: "Group"}, func(spec types.Spec) (Runnables, error) { + if spec.Properties == nil { + return nil, nil + } + + type t struct { + Instance struct { + Plugin plugin.Name + Properties *types.Any + Options *types.Any + } + Flavor struct { + Plugin plugin.Name + Properties *types.Any + Options *types.Any + } + } + + groupSpec := t{} + err := spec.Properties.Decode(&groupSpec) + if err != nil { + return nil, err + } + + return Runnables{ + AsRunnable(types.Spec{ + Kind: groupSpec.Instance.Plugin.Lookup(), + Metadata: types.Metadata{ + Name: groupSpec.Instance.Plugin.String(), + }, + Properties: groupSpec.Instance.Properties, + Options: groupSpec.Instance.Options, + }), + AsRunnable(types.Spec{ + Kind: groupSpec.Flavor.Plugin.Lookup(), + Metadata: types.Metadata{ + Name: groupSpec.Flavor.Plugin.String(), + }, + Properties: groupSpec.Flavor.Properties, + Options: groupSpec.Flavor.Options, + }), + }, nil + }) + + runnables, err := RunnablesFrom(mustSpecs(types.SpecsFromString(` +- kind: group + version: Group + metadata: + name: us-east-compute/workers + properties: + Allocation: + Size: 2 + Flavor: + Plugin: vanilla + Properties: + Attachments: + - ID: attachid + Type: attachtype + Init: + - docker pull nginx:alpine + - docker run -d -p 80:80 nginx-alpine + Tags: + project: infrakit + tier: web + Instance: + Plugin: simulator/compute + Properties: + Note: Instance properties version 1.0 + options: + poll: 10 +`))) + + require.NoError(t, err) + require.Equal(t, "group", runnables[0].Kind()) + require.Equal(t, "us-east-compute", runnables[0].Plugin().Lookup()) + require.Equal(t, "simulator", runnables[1].Kind()) + require.Equal(t, "simulator", runnables[1].Plugin().Lookup()) + require.Equal(t, "vanilla", runnables[2].Kind()) + require.Equal(t, "vanilla", runnables[2].Plugin().Lookup()) + + runnables, err = RunnablesFrom(mustSpecs(types.SpecsFromString(` +- kind: group + version: Group + metadata: + name: us-east-compute/workers + properties: + Allocation: + Size: 2 + Flavor: + Plugin: vanilla + Properties: + Attachments: + - ID: attachid + Type: attachtype + Init: + - docker pull nginx:alpine + - docker run -d -p 80:80 nginx-alpine + Tags: + project: infrakit + tier: web + Instance: + Plugin: simulator/compute + Properties: + Note: Instance properties version 1.0 + options: + poll: 10 + +- kind: ingress + metadata: + name: us-east-net/workers.com + properties: + routes: 10 + options: + poll: 20 +`))) + + require.NoError(t, err) + require.Equal(t, "group", runnables[0].Kind()) + require.Equal(t, "us-east-compute", runnables[0].Plugin().Lookup()) + require.Equal(t, "ingress", runnables[1].Kind()) + require.Equal(t, "us-east-net", runnables[1].Plugin().Lookup()) + require.Equal(t, "simulator", runnables[2].Kind()) + require.Equal(t, "simulator", runnables[2].Plugin().Lookup()) + require.Equal(t, "vanilla", runnables[3].Kind()) + require.Equal(t, "vanilla", runnables[3].Plugin().Lookup()) + + require.Equal(t, "{\"poll\":10}", runnables[0].Options().String()) + options := map[string]interface{}{} + require.NoError(t, runnables[1].Options().Decode(&options)) + require.Equal(t, float64(20), options["poll"]) + + runnables, err = RunnablesFrom(mustSpecs(types.SpecsFromString(` +- kind: ingress + metadata: + name: us-east-net/workers.com + properties: + routes: 10 + options: + poll: 20 + +# The lookup will be nfs (a plugin endpoint) +- kind: simulator/disk + metadata: + name: nfs/disk1 + properties: + cidr: 10.20.100.100/16 + +# The lookup will default to simulator because it's not in the metadata/name +- kind: simulator/net + metadata: + name: subnet1 + properties: + cidr: 10.20.100.100/16 + + +`))) + + require.NoError(t, err) + require.Equal(t, "ingress", runnables[0].Kind()) + require.Equal(t, "us-east-net", runnables[0].Plugin().Lookup()) + require.Equal(t, "simulator", runnables[1].Kind()) + require.Equal(t, "nfs", runnables[1].Plugin().Lookup()) + require.Equal(t, "simulator", runnables[2].Kind()) + require.Equal(t, "simulator", runnables[2].Plugin().Lookup()) + +} diff --git a/pkg/run/manager/manager.go b/pkg/run/manager/manager.go index 396b3bbd1..d2fb8dcd4 100644 --- a/pkg/run/manager/manager.go +++ b/pkg/run/manager/manager.go @@ -190,6 +190,7 @@ func (m *Manager) Launch(exec string, key string, name plugin.Name, options *typ lookup, _ := name.GetLookupAndType() if countMatches([]string{lookup}, running) > 0 { + log.Debug("already running", "lookup", lookup, "name", name) m.started <- name return nil } diff --git a/pkg/types/interface_spec.go b/pkg/types/interface_spec.go index 9922619cd..b491bf33f 100644 --- a/pkg/types/interface_spec.go +++ b/pkg/types/interface_spec.go @@ -12,18 +12,31 @@ type InterfaceSpec struct { // Version is the identifier for the API version. Version string + + // Sub is the name of 'subclass' entity that follows the general contract but has a distinguishing name + Sub string } // Encode encodes a struct form to string func (i InterfaceSpec) Encode() string { - return fmt.Sprintf("%s/%s", i.Name, i.Version) + if i.Sub == "" { + return fmt.Sprintf("%s/%s", i.Name, i.Version) + } + return fmt.Sprintf("%s/%s/%s", i.Name, i.Version, i.Sub) } // DecodeInterfaceSpec takes a string and returns the struct func DecodeInterfaceSpec(s string) InterfaceSpec { - p := strings.SplitN(s, "/", 2) - return InterfaceSpec{ + p := strings.SplitN(s, "/", 3) + if len(p) == 1 { + return InterfaceSpec{Name: s} + } + i := InterfaceSpec{ Name: p[0], Version: p[1], } + if len(p) == 3 { + i.Sub = p[2] + } + return i } From dcfa577acb7f5044c0d9994c1b30f73dcf82acb0 Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Oct 2017 10:07:50 -0700 Subject: [PATCH 07/11] move logic for analyzing input for plugins to start to a pkg Signed-off-by: David Chung --- cmd/infrakit/util/init/init.go | 52 ++++++++--------------- pkg/run/scope/local/depends.go | 75 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 pkg/run/scope/local/depends.go diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index 6bf174ce3..a0fd4b4ce 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -7,7 +7,6 @@ import ( "time" "github.com/docker/infrakit/pkg/cli" - "github.com/docker/infrakit/pkg/core" "github.com/docker/infrakit/pkg/discovery" "github.com/docker/infrakit/pkg/launch" logutil "github.com/docker/infrakit/pkg/log" @@ -16,7 +15,6 @@ import ( metadata_template "github.com/docker/infrakit/pkg/plugin/metadata/template" flavor_rpc "github.com/docker/infrakit/pkg/rpc/flavor" metadata_rpc "github.com/docker/infrakit/pkg/rpc/metadata" - "github.com/docker/infrakit/pkg/run/depends" "github.com/docker/infrakit/pkg/run/manager" "github.com/docker/infrakit/pkg/run/scope" "github.com/docker/infrakit/pkg/run/scope/local" @@ -164,31 +162,22 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { return err } - // TODO - update the schema soon. This is the Plugin/Properties schema - type spec struct { - Plugin plugin.Name - Properties struct { - ID group.ID - Properties group_types.Spec - } - } - - specs := []spec{} - err = types.AnyString(input).Decode(&specs) + found := false + var groupSpec group_types.Spec + var gid group.ID + err = local.ParseInputSpecs([]byte(input), + func(id group.ID, s group_types.Spec) { + if string(id) == *groupID { + gid = id + groupSpec = s + found = true + } + }) if err != nil { + log.Error("parsing input", "err", err) return err } - - var groupSpec *group_types.Spec - for _, s := range specs { - if string(s.Properties.ID) == *groupID { - copy := s.Properties.Properties - groupSpec = © - break - } - } - - if groupSpec == nil { + if !found { return fmt.Errorf("no such group: %v", *groupID) } @@ -201,18 +190,11 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { for _, start := range *starts { targets = append(targets, local.StartPlugin(start)) } - - targets = append(targets, local.FromAddressable(core.AddressableFromPluginName(groupSpec.Flavor.Plugin))) - - if spec, err := toSpec(group.ID(*groupID), *groupSpec); err == nil { - log.Debug("resolving", "groupID", *groupID, "spec", spec) - if other, err := depends.Resolve(spec, spec.Kind, nil); err == nil { - for _, r := range other { - targets = append(targets, local.FromAddressable(r)) - } - } + more, err := local.Plugins(gid, groupSpec) + if err != nil { + return targets, err } - + targets = append(targets, more...) log.Info("plugins to start", "targets", targets) return } diff --git a/pkg/run/scope/local/depends.go b/pkg/run/scope/local/depends.go new file mode 100644 index 000000000..058fa0cff --- /dev/null +++ b/pkg/run/scope/local/depends.go @@ -0,0 +1,75 @@ +package local + +import ( + "github.com/docker/infrakit/pkg/plugin" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" + "github.com/docker/infrakit/pkg/run/depends" + group_kind "github.com/docker/infrakit/pkg/run/v0/group" + "github.com/docker/infrakit/pkg/spi/group" + "github.com/docker/infrakit/pkg/types" +) + +// ParseInputSpecs parses the input bytes which is the groups.json, and calls +// each time a group spec is found. +func ParseInputSpecs(input []byte, foundGroupSpec func(group.ID, group_types.Spec)) error { + // TODO - update the schema soon. This is the Plugin/Properties schema + type spec struct { + Plugin plugin.Name + Properties struct { + ID group.ID + Properties group_types.Spec + } + } + + specs := []spec{} + err := types.AnyBytes(input).Decode(&specs) + if err != nil { + return err + } + for _, s := range specs { + foundGroupSpec(s.Properties.ID, s.Properties.Properties) + } + return nil +} + +// Plugins returns a list of startPlugin directives from the input. +// This will recurse into any composable plugins. +func Plugins(gid group.ID, gspec group_types.Spec) ([]StartPlugin, error) { + targets := []StartPlugin{} + + spec, err := toSpec(gid, gspec) + if err != nil { + return nil, err + } + + log.Debug("resolving", "groupID", gid, "spec", spec) + other, err := depends.Resolve(spec, spec.Kind, nil) + if err != nil { + return nil, err + } + + for _, r := range other { + targets = append(targets, FromAddressable(r)) + } + + return targets, nil +} + +func toSpec(gid group.ID, g group_types.Spec) (spec types.Spec, err error) { + any, e := types.AnyValue(g) + if e != nil { + err = e + return + } + spec = types.Spec{ + Kind: group_kind.Kind, + Version: group.InterfaceSpec.Encode(), + Metadata: types.Metadata{ + Identity: &types.Identity{ID: string(gid)}, + Name: plugin.NameFrom(group_kind.Kind, string(gid)).String(), + }, + Properties: any, + Options: nil, // TOOD -- the old format doesn't have this information. + } + return +} From 29ccb4d57cad554f168b7ca94d64a62e80891730 Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Oct 2017 11:15:18 -0700 Subject: [PATCH 08/11] make the manager commit use common pkg Signed-off-by: David Chung --- cmd/infrakit/manager/manager.go | 53 +++++++++++-------------------- cmd/infrakit/manager/schema/v0.go | 34 ++++++++++++++++++++ cmd/infrakit/util/init/init.go | 28 ++++------------ pkg/run/scope/local/depends.go | 42 ++++++++++++------------ 4 files changed, 80 insertions(+), 77 deletions(-) create mode 100644 cmd/infrakit/manager/schema/v0.go diff --git a/cmd/infrakit/manager/manager.go b/cmd/infrakit/manager/manager.go index 63fb7a6f6..5c0e3e335 100644 --- a/cmd/infrakit/manager/manager.go +++ b/cmd/infrakit/manager/manager.go @@ -7,11 +7,14 @@ import ( "strings" "github.com/docker/infrakit/cmd/infrakit/base" + "github.com/docker/infrakit/cmd/infrakit/manager/schema" + "github.com/docker/infrakit/pkg/cli" "github.com/docker/infrakit/pkg/discovery" logutil "github.com/docker/infrakit/pkg/log" "github.com/docker/infrakit/pkg/manager" "github.com/docker/infrakit/pkg/plugin" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/rpc/client" group_rpc "github.com/docker/infrakit/pkg/rpc/group" manager_rpc "github.com/docker/infrakit/pkg/rpc/manager" @@ -113,54 +116,36 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { return err } - // In any case, the view should be in JSON format - - // Treat this as an Any and then convert - any := types.AnyString(view) - - groups := []plugin.Spec{} - err = any.Decode(&groups) - if err != nil { - log.Warn("Error parsing the template for plugin specs.") - return err - } - - // Check the list of plugins - for _, gp := range groups { - - endpoint, err := plugins().Find(gp.Plugin) + commitEachGroup := func(name plugin.Name, gid group.ID, gspec group_types.Spec) error { + endpoint, err := plugins().Find(name) if err != nil { return err } - - // unmarshal the group spec - spec := group.Spec{} - if gp.Properties != nil { - err = gp.Properties.Decode(&spec) - if err != nil { - return err - } - } - - // TODO(chungers) -- we need to enforce and confirm the type of this. - // Right now we assume the RPC endpoint is indeed a group. target, err := group_rpc.NewClient(endpoint.Address) + log.Debug("commit", "plugin", name, "address", endpoint.Address, "err", err, "gspec", gspec) - log.Debug("commit", "plugin", gp.Plugin, "address", endpoint.Address, "err", err, "spec", spec) + if err != nil { + return err + } + any, err := types.AnyValue(gspec) if err != nil { return err } - plan, err := target.CommitGroup(spec, *pretend) + plan, err := target.CommitGroup(group.Spec{ + ID: gid, + Properties: any, + }, *pretend) + if err != nil { return err } - fmt.Println("Group", spec.ID, "with plugin", gp.Plugin, "plan:", plan) + fmt.Println("Group", gid, "with plugin", name, "plan:", plan) + return nil } - - return nil + return schema.ParseInputSpecs([]byte(view), commitEachGroup) }, } commit.Flags().AddFlagSet(templateFlags) @@ -357,7 +342,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { } change.AddCommand(changeList, changeGet) - cmd.AddCommand(commit, inspect, change, leader) + cmd.AddCommand(commit, inspect, leader) return cmd } diff --git a/cmd/infrakit/manager/schema/v0.go b/cmd/infrakit/manager/schema/v0.go new file mode 100644 index 000000000..0877ab579 --- /dev/null +++ b/cmd/infrakit/manager/schema/v0.go @@ -0,0 +1,34 @@ +package schema + +import ( + "github.com/docker/infrakit/pkg/plugin" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" + "github.com/docker/infrakit/pkg/spi/group" + "github.com/docker/infrakit/pkg/types" +) + +// ParseInputSpecs parses the input bytes which is the groups.json, and calls +// each time a group spec is found. +func ParseInputSpecs(input []byte, foundGroupSpec func(plugin.Name, group.ID, group_types.Spec) error) error { + // TODO - update the schema soon. This is the Plugin/Properties schema + type spec struct { + Plugin plugin.Name + Properties struct { + ID group.ID + Properties group_types.Spec + } + } + + specs := []spec{} + err := types.AnyBytes(input).Decode(&specs) + if err != nil { + return err + } + for _, s := range specs { + err = foundGroupSpec(s.Plugin, s.Properties.ID, s.Properties.Properties) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index a0fd4b4ce..ec61c365e 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -6,6 +6,8 @@ import ( "strings" "time" + "github.com/docker/infrakit/cmd/infrakit/manager/schema" + "github.com/docker/infrakit/pkg/cli" "github.com/docker/infrakit/pkg/discovery" "github.com/docker/infrakit/pkg/launch" @@ -18,7 +20,6 @@ import ( "github.com/docker/infrakit/pkg/run/manager" "github.com/docker/infrakit/pkg/run/scope" "github.com/docker/infrakit/pkg/run/scope/local" - group_kind "github.com/docker/infrakit/pkg/run/v0/group" "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/spi/metadata" @@ -28,25 +29,6 @@ import ( var log = logutil.New("module", "cmd/infrakit/util/init") -func toSpec(gid group.ID, g group_types.Spec) (spec types.Spec, err error) { - any, e := types.AnyValue(g) - if e != nil { - err = e - return - } - spec = types.Spec{ - Kind: group_kind.Kind, - Version: group.InterfaceSpec.Encode(), - Metadata: types.Metadata{ - Identity: &types.Identity{ID: string(gid)}, - Name: plugin.NameFrom(group_kind.Kind, string(gid)).String(), - }, - Properties: any, - Options: nil, // TOOD -- the old format doesn't have this information. - } - return -} - func getPluginManager(plugins func() discovery.Plugins, services *cli.Services, configURL string) (*manager.Manager, error) { @@ -165,13 +147,15 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { found := false var groupSpec group_types.Spec var gid group.ID - err = local.ParseInputSpecs([]byte(input), - func(id group.ID, s group_types.Spec) { + + err = schema.ParseInputSpecs([]byte(input), + func(name plugin.Name, id group.ID, s group_types.Spec) error { if string(id) == *groupID { gid = id groupSpec = s found = true } + return nil }) if err != nil { log.Error("parsing input", "err", err) diff --git a/pkg/run/scope/local/depends.go b/pkg/run/scope/local/depends.go index 058fa0cff..41e8ee3c5 100644 --- a/pkg/run/scope/local/depends.go +++ b/pkg/run/scope/local/depends.go @@ -9,28 +9,28 @@ import ( "github.com/docker/infrakit/pkg/types" ) -// ParseInputSpecs parses the input bytes which is the groups.json, and calls -// each time a group spec is found. -func ParseInputSpecs(input []byte, foundGroupSpec func(group.ID, group_types.Spec)) error { - // TODO - update the schema soon. This is the Plugin/Properties schema - type spec struct { - Plugin plugin.Name - Properties struct { - ID group.ID - Properties group_types.Spec - } - } +// // ParseInputSpecs parses the input bytes which is the groups.json, and calls +// // each time a group spec is found. +// func ParseInputSpecs(input []byte, foundGroupSpec func(group.ID, group_types.Spec)) error { +// // TODO - update the schema soon. This is the Plugin/Properties schema +// type spec struct { +// Plugin plugin.Name +// Properties struct { +// ID group.ID +// Properties group_types.Spec +// } +// } - specs := []spec{} - err := types.AnyBytes(input).Decode(&specs) - if err != nil { - return err - } - for _, s := range specs { - foundGroupSpec(s.Properties.ID, s.Properties.Properties) - } - return nil -} +// specs := []spec{} +// err := types.AnyBytes(input).Decode(&specs) +// if err != nil { +// return err +// } +// for _, s := range specs { +// foundGroupSpec(s.Properties.ID, s.Properties.Properties) +// } +// return nil +// } // Plugins returns a list of startPlugin directives from the input. // This will recurse into any composable plugins. From 5c0ecf4f03e2d2bec67e816f14884aa8ec9a7b65 Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Oct 2017 12:21:27 -0700 Subject: [PATCH 09/11] util/init, plugin/start, and up -- use common plugin manager helper Signed-off-by: David Chung --- cmd/infrakit/plugin/plugin.go | 33 ++---- cmd/infrakit/up/up.go | 187 ++++++++++++++------------------- cmd/infrakit/util/init/init.go | 2 +- pkg/cli/plugins.go | 34 ++++++ 4 files changed, 123 insertions(+), 133 deletions(-) create mode 100644 pkg/cli/plugins.go diff --git a/cmd/infrakit/plugin/plugin.go b/cmd/infrakit/plugin/plugin.go index 234608e93..f8559707a 100644 --- a/cmd/infrakit/plugin/plugin.go +++ b/cmd/infrakit/plugin/plugin.go @@ -7,6 +7,8 @@ import ( "time" "github.com/docker/infrakit/cmd/infrakit/base" + + "github.com/docker/infrakit/pkg/cli" "github.com/docker/infrakit/pkg/discovery" "github.com/docker/infrakit/pkg/launch" logutil "github.com/docker/infrakit/pkg/log" @@ -16,7 +18,6 @@ import ( "github.com/docker/infrakit/pkg/run/manager" group_kind "github.com/docker/infrakit/pkg/run/v0/group" manager_kind "github.com/docker/infrakit/pkg/run/v0/manager" - "github.com/docker/infrakit/pkg/types" "github.com/spf13/cobra" ) @@ -139,9 +140,9 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { } configURL := start.Flags().String("config-url", "", "URL for the startup configs") - mustAll := start.Flags().Bool("all", false, "Panic if any plugin fails to start") - templateFlags, toJSON, _, processTemplate := base.TemplateProcessor(plugins) - start.Flags().AddFlagSet(templateFlags) + + services := cli.NewServices(plugins) + start.Flags().AddFlagSet(services.ProcessTemplateFlags) start.RunE = func(c *cobra.Command, args []string) error { @@ -149,32 +150,12 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { panic("no plugins()") } - parsedRules := []launch.Rule{} - log.Info("config", "url", *configURL) - - if *configURL != "" { - buff, err := processTemplate(*configURL) - if err != nil { - return err - } - - view, err := toJSON([]byte(buff)) - if err != nil { - return err - } - - configs := types.AnyBytes(view) - err = configs.Decode(&parsedRules) - if err != nil { - return err - } - } - - pluginManager, err := manager.ManagePlugins(parsedRules, plugins, *mustAll, 5*time.Second) + pluginManager, err := cli.PluginManager(plugins, services, *configURL) if err != nil { return err } + defer func() { if r := recover(); r != nil { log.Error("Error occurred. Recovered but exiting.", "err", r) diff --git a/cmd/infrakit/up/up.go b/cmd/infrakit/up/up.go index 754e72eac..37a7eeca2 100644 --- a/cmd/infrakit/up/up.go +++ b/cmd/infrakit/up/up.go @@ -2,19 +2,21 @@ package up import ( "fmt" + "strings" "time" "github.com/docker/infrakit/cmd/infrakit/base" + "github.com/docker/infrakit/cmd/infrakit/manager/schema" + + "github.com/docker/infrakit/pkg/cli" "github.com/docker/infrakit/pkg/discovery" - "github.com/docker/infrakit/pkg/launch" - "github.com/docker/infrakit/pkg/launch/inproc" logutil "github.com/docker/infrakit/pkg/log" - "github.com/docker/infrakit/pkg/manager" "github.com/docker/infrakit/pkg/plugin" - "github.com/docker/infrakit/pkg/run" - run_manager "github.com/docker/infrakit/pkg/run/manager" - group_kind "github.com/docker/infrakit/pkg/run/v0/group" - manager_kind "github.com/docker/infrakit/pkg/run/v0/manager" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" + metadata_template "github.com/docker/infrakit/pkg/plugin/metadata/template" + "github.com/docker/infrakit/pkg/run/scope" + "github.com/docker/infrakit/pkg/run/scope/local" + "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/types" "github.com/spf13/cobra" ) @@ -28,38 +30,17 @@ func init() { // Command is the entrypoint func Command(plugins func() discovery.Plugins) *cobra.Command { - templateFlags, toJSON, _, processTemplate := base.TemplateProcessor(plugins) - - loadRules := func(url string) ([]launch.Rule, error) { - rules := []launch.Rule{} - if buff, err := processTemplate(url); err != nil { - return nil, err - } else if view, err := toJSON([]byte(buff)); err != nil { - return nil, err - } else if err = types.AnyBytes(view).Decode(&rules); err != nil { - return nil, err - } - return rules, nil - } - loadSpecs := func(url string) ([]types.Spec, error) { - specs := []types.Spec{} - if buff, err := processTemplate(url); err != nil { - return nil, err - } else if view, err := toJSON([]byte(buff)); err != nil { - return nil, err - } else if err = types.AnyBytes(view).Decode(&specs); err != nil { - return nil, err - } - return specs, nil - } + services := cli.NewServices(plugins) up := &cobra.Command{ Use: "up ", Short: "Up everything", } - launchConfigURL := up.Flags().String("launch-config-url", "", "URL for the startup configs") - up.Flags().AddFlagSet(templateFlags) + waitDuration := up.Flags().String("wait", "1s", "Wait for plugins to be ready") + configURL := up.Flags().String("config-url", "", "URL for the startup configs") + up.Flags().AddFlagSet(services.ProcessTemplateFlags) + metadatas := up.Flags().StringSlice("metadata", []string{}, "key=value to set metadata") up.RunE = func(c *cobra.Command, args []string) error { @@ -71,94 +52,88 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { return fmt.Errorf("missing url arg") } - // Now the actual spec of the infrastructure to stand up - specsURL := args[0] + pluginManager, err := cli.PluginManager(plugins, services, *configURL) + if err != nil { + return err + } + + wait := types.MustParseDuration(*waitDuration) - launchRules := []launch.Rule{} - // parse the launch rules if any - if *launchConfigURL != "" { - list, err := loadRules(*launchConfigURL) + log.Info("Starting up base plugins") + basePlugins := []string{"vars", "manager"} + for _, base := range basePlugins { + execName, kind, name, _ := local.StartPlugin(base).Parse() + err := pluginManager.Launch(execName, kind, name, nil) if err != nil { + log.Error("cannot start base plugin", "spec", base) return err } - launchRules = list } - - // Plugin launcher runs asynchronously - pluginManager, err := run_manager.ManagePlugins(launchRules, plugins, false, 5*time.Second) - if err != nil { - return err + pluginManager.WaitStarting() + <-time.After(wait.Duration()) + + if len(*metadatas) > 0 { + log.Info("Setting metadata entries") + mfunc := metadata_template.MetadataFunc(plugins) + for _, md := range *metadatas { + // TODO -- this is not transactional.... we don't know + // the paths and there may be changes to multiple metadata + // plugins. For now we just process one by one. + kv := strings.Split(md, "=") + if len(kv) == 2 { + _, err := mfunc(kv[0], kv[1]) + if err != nil { + return err + } + log.Info("written metadata", "key", kv[0], "value", kv[1]) + } + } } - defer pluginManager.Stop() - // start up the basics - err = pluginManager.Launch(inproc.ExecName, manager_kind.Kind, plugin.Name(manager_kind.LookupName), nil) - if err != nil { - return err - } - err = pluginManager.Launch(inproc.ExecName, group_kind.Kind, plugin.Name(group_kind.LookupName), nil) + log.Info("Parsing the input groups.json as template") + input, err := services.ReadFromStdinIfElse( + func() bool { return args[0] == "-" }, + func() (string, error) { return services.ProcessTemplate(args[0]) }, + services.ToJSON, + ) if err != nil { + log.Error("processing input", "err", err) return err } - pluginManager.WaitStarting() - - log.Info("Entering main loop") - - tick := time.Tick(5 * time.Second) - stop := make(chan struct{}) - - go func() { - - main: - for { - select { - case <-tick: + targets := []local.StartPlugin{} + err = schema.ParseInputSpecs([]byte(input), + func(name plugin.Name, id group.ID, s group_types.Spec) error { - // commit the specs to the manager - case <-stop: - log.Info("Stopped checking for config changes") - return - } - - // refresh the specs from the url - log.Info("Checking", "url", specsURL) - specs, err := loadSpecs(specsURL) + more, err := local.Plugins(id, s) if err != nil { - log.Error("Error loading specs", "url", specsURL, "err", err) - continue main + return err } + targets = append(targets, more...) + return nil + }) + if err != nil { + log.Error("parsing input", "err", err) + return err + } - // from the specs get the plugin names and kind to start - log.Debug("Loaded specs", "specs", specs, "V", logutil.V(200)) - err = pluginManager.StartPluginsFromSpecs(specs, - func(err error) bool { - log.Error("cannot start plugin", "err", err) - return false - }) - - if err != nil { - log.Error("Error from input. Not committing.", "err", err) - continue main - } - - // Now tell the manager to enforce - err = run.Call(plugins, manager.InterfaceSpec, nil, - func(m manager.Manager) error { - log.Debug("Calling manager to enforce", "m", m, "specs", specs) - return m.Enforce(specs) - }) - if err != nil { - log.Error("Error making call to manager", "err", err) - } - - } - }() - - pluginManager.WaitForAllShutdown() - log.Info("All plugins shutdown") - close(stop) - return nil + return local.Execute(plugins, pluginManager, + func() ([]local.StartPlugin, error) { + log.Info("plugins to start", "targets", targets) + return targets, nil + }, + func(scope scope.Scope) error { + // TODO - in here loop and commit periodically + + pluginManager.WaitForAllShutdown() + + return nil + }, + local.Options{ + StartWait: wait, + StopWait: wait, + }, + ) } return up diff --git a/cmd/infrakit/util/init/init.go b/cmd/infrakit/util/init/init.go index ec61c365e..f8e2dbfd2 100644 --- a/cmd/infrakit/util/init/init.go +++ b/cmd/infrakit/util/init/init.go @@ -94,7 +94,7 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { wait := types.MustParseDuration(*waitDuration) - pluginManager, err := getPluginManager(plugins, services, *configURL) + pluginManager, err := cli.PluginManager(plugins, services, *configURL) if err != nil { return err } diff --git a/pkg/cli/plugins.go b/pkg/cli/plugins.go new file mode 100644 index 000000000..635c5090d --- /dev/null +++ b/pkg/cli/plugins.go @@ -0,0 +1,34 @@ +package cli + +import ( + "time" + + "github.com/docker/infrakit/pkg/discovery" + "github.com/docker/infrakit/pkg/launch" + "github.com/docker/infrakit/pkg/run/manager" + "github.com/docker/infrakit/pkg/types" +) + +// PluginManager returns the plugin manager for running plugins locally. +func PluginManager(plugins func() discovery.Plugins, + services *Services, configURL string) (*manager.Manager, error) { + + parsedRules := []launch.Rule{} + + if configURL != "" { + buff, err := services.ProcessTemplate(configURL) + if err != nil { + return nil, err + } + view, err := services.ToJSON([]byte(buff)) + if err != nil { + return nil, err + } + configs := types.AnyBytes(view) + err = configs.Decode(&parsedRules) + if err != nil { + return nil, err + } + } + return manager.ManagePlugins(parsedRules, plugins, true, 5*time.Second) +} From 66395d9d298c5a0de1b5fbbae40ea308f21956ed Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Oct 2017 18:15:20 -0700 Subject: [PATCH 10/11] fix lint Signed-off-by: David Chung --- pkg/core/addressable.go | 4 ++-- pkg/core/addressable_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/core/addressable.go b/pkg/core/addressable.go index 882adebb5..b542f472d 100644 --- a/pkg/core/addressable.go +++ b/pkg/core/addressable.go @@ -49,9 +49,9 @@ func NewAddressableFromMetadata(kind string, metadata types.Metadata) Addressabl return NewAddressable(kind, plugin.Name(metadata.Name), instance) } -// NewAddressable returns a generic addressable object from just the plugin name. +// NewAddressableFromPluginName returns a generic addressable object from just the plugin name. // The kind is assume to be the same as the lookup. -func AddressableFromPluginName(pn plugin.Name) Addressable { +func NewAddressableFromPluginName(pn plugin.Name) Addressable { return NewAddressable(pn.Lookup(), pn, "") } diff --git a/pkg/core/addressable_test.go b/pkg/core/addressable_test.go index f0aa9d39a..445f33764 100644 --- a/pkg/core/addressable_test.go +++ b/pkg/core/addressable_test.go @@ -54,7 +54,7 @@ metadata: require.Equal(t, "group-stateless/mygroup", string(c.Plugin())) require.Equal(t, "mygroup", c.Instance()) - c = AddressableFromPluginName(plugin.Name("swarm/manager")) + c = NewAddressableFromPluginName(plugin.Name("swarm/manager")) require.Equal(t, "swarm", c.Kind()) require.Equal(t, "swarm/manager", string(c.Plugin())) require.Equal(t, "swarm", c.Plugin().Lookup()) From 8bed625dc4150b247fadd09f3b7eaabf8254ac4d Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 26 Oct 2017 09:40:57 -0700 Subject: [PATCH 11/11] fix broken test - use better strings/ encoding of interface spec Signed-off-by: David Chung --- pkg/rpc/client/handshake.go | 2 +- pkg/rpc/client/handshake_test.go | 2 +- pkg/types/interface_spec.go | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/rpc/client/handshake.go b/pkg/rpc/client/handshake.go index 596ccf09a..dbe5dc252 100644 --- a/pkg/rpc/client/handshake.go +++ b/pkg/rpc/client/handshake.go @@ -49,7 +49,7 @@ func (c *handshakingClient) handshake() error { return err } - err = fmt.Errorf("Plugin does not support interface %v", c.iface) + err = fmt.Errorf("Plugin does not support interface %v", c.iface.Encode()) for _, iface := range apis { if iface.Name == c.iface.Name { if iface.Version == c.iface.Version { diff --git a/pkg/rpc/client/handshake_test.go b/pkg/rpc/client/handshake_test.go index 954ca72a5..db86e67fb 100644 --- a/pkg/rpc/client/handshake_test.go +++ b/pkg/rpc/client/handshake_test.go @@ -72,7 +72,7 @@ func TestHandshakeFailWrongAPI(t *testing.T) { client := rpcClient{client: r} err = client.DoSomething() require.Error(t, err) - require.Equal(t, "Plugin does not support interface {OtherPlugin 0.1.0}", err.Error()) + require.Equal(t, "Plugin does not support interface OtherPlugin/0.1.0", err.Error()) } type rpcClient struct { diff --git a/pkg/types/interface_spec.go b/pkg/types/interface_spec.go index b491bf33f..3c6f8adff 100644 --- a/pkg/types/interface_spec.go +++ b/pkg/types/interface_spec.go @@ -17,6 +17,11 @@ type InterfaceSpec struct { Sub string } +// String implements the stringer for fmt printing +func (i InterfaceSpec) String() string { + return i.Encode() +} + // Encode encodes a struct form to string func (i InterfaceSpec) Encode() string { if i.Sub == "" {