From 7c7263adc25de1f1371de4312f3bb31bfde10c97 Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Jan 2017 11:48:43 -0800 Subject: [PATCH 01/10] initial SPI signature change Signed-off-by: David Chung --- pkg/spi/instance/spi.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/spi/instance/spi.go b/pkg/spi/instance/spi.go index 8fc5d1832..2fbfa2312 100644 --- a/pkg/spi/instance/spi.go +++ b/pkg/spi/instance/spi.go @@ -1,24 +1,27 @@ package instance import ( - "encoding/json" "github.com/docker/infrakit/pkg/spi" + "github.com/docker/infrakit/pkg/types" ) // InterfaceSpec is the current name and version of the Instance API. var InterfaceSpec = spi.InterfaceSpec{ Name: "Instance", - Version: "0.1.0", + Version: "0.3.0", } // Plugin is a vendor-agnostic API used to create and manage resources with an infrastructure provider. type Plugin interface { // Validate performs local validation on a provision request. - Validate(req json.RawMessage) error + Validate(req *types.Any) error // Provision creates a new instance based on the spec. Provision(spec Spec) (*ID, error) + // Label labels the instance + Label(instance ID, labels map[string]string) error + // Destroy terminates an existing instance. Destroy(instance ID) error From b4411082ac1be1bc02f5b9394826f6b99f36aeea Mon Sep 17 00:00:00 2001 From: David Chung Date: Wed, 25 Jan 2017 15:37:52 -0800 Subject: [PATCH 02/10] change json.RawMessage -> types.Any; make spi consistent Signed-off-by: David Chung --- cmd/cli/flavor.go | 13 +++-- cmd/cli/instance.go | 3 +- pkg/mock/spi/flavor/flavor.go | 10 ++-- pkg/mock/spi/instance/instance.go | 14 ++++- pkg/plugin/group/group.go | 10 ++-- pkg/plugin/group/integration_test.go | 12 ++-- pkg/plugin/group/scaled.go | 14 ++--- pkg/plugin/group/testplugin.go | 32 ++++++----- pkg/plugin/group/types/types.go | 20 ------- pkg/rpc/flavor/client.go | 25 ++++---- pkg/rpc/flavor/rpc_multi_test.go | 60 ++++++++++---------- pkg/rpc/flavor/rpc_test.go | 85 ++++++++++++++-------------- pkg/rpc/flavor/service.go | 22 ++----- pkg/rpc/flavor/types.go | 17 +++--- pkg/rpc/group/service.go | 4 +- pkg/rpc/instance/client.go | 20 +++++-- pkg/rpc/instance/rpc_multi_test.go | 38 ++++++------- pkg/rpc/instance/rpc_test.go | 36 +++++++----- pkg/rpc/instance/service.go | 23 +++++--- pkg/rpc/instance/types.go | 18 +++++- pkg/rpc/server/info_test.go | 7 ++- pkg/rpc/server/reflector.go | 4 +- pkg/rpc/server/reflector_test.go | 11 ++-- pkg/rpc/server/server_test.go | 4 +- pkg/spi/flavor/spi.go | 12 ++-- pkg/spi/instance/spi.go | 2 +- pkg/spi/instance/types.go | 4 +- pkg/spi/plugin.go | 4 +- pkg/template/fetch_test.go | 12 +++- pkg/types/any.go | 8 +++ 30 files changed, 291 insertions(+), 253 deletions(-) diff --git a/cmd/cli/flavor.go b/cmd/cli/flavor.go index 4d17fff40..5473b35ef 100644 --- a/cmd/cli/flavor.go +++ b/cmd/cli/flavor.go @@ -10,10 +10,11 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/infrakit/pkg/discovery" "github.com/docker/infrakit/pkg/plugin" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" flavor_plugin "github.com/docker/infrakit/pkg/rpc/flavor" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/spf13/cobra" ) @@ -57,13 +58,13 @@ func flavorPluginCommand(plugins func() discovery.Plugins) *cobra.Command { "Group Size to use as the Allocation method") } - allocationMethodFromFlags := func() types.AllocationMethod { + allocationMethodFromFlags := func() group_types.AllocationMethod { ids := []instance.LogicalID{} for _, id := range logicalIDs { ids = append(ids, instance.LogicalID(id)) } - return types.AllocationMethod{ + return group_types.AllocationMethod{ Size: groupSize, LogicalIDs: ids, } @@ -86,7 +87,7 @@ func flavorPluginCommand(plugins func() discovery.Plugins) *cobra.Command { os.Exit(1) } - return flavorPlugin.Validate(json.RawMessage(buff), allocationMethodFromFlags()) + return flavorPlugin.Validate(types.AnyBytes(buff), allocationMethodFromFlags()) }, } addAllocationMethodFlags(validate) @@ -121,7 +122,7 @@ func flavorPluginCommand(plugins func() discovery.Plugins) *cobra.Command { } spec, err = flavorPlugin.Prepare( - json.RawMessage(flavorProperties), + types.AnyBytes(flavorProperties), spec, allocationMethodFromFlags()) if err == nil { @@ -179,7 +180,7 @@ func flavorPluginCommand(plugins func() discovery.Plugins) *cobra.Command { desc.LogicalID = &logical } - healthy, err := flavorPlugin.Healthy(json.RawMessage(flavorProperties), desc) + healthy, err := flavorPlugin.Healthy(types.AnyBytes(flavorProperties), desc) if err == nil { fmt.Printf("%v\n", healthy) } diff --git a/cmd/cli/instance.go b/cmd/cli/instance.go index 84a50b1b6..4967d2989 100644 --- a/cmd/cli/instance.go +++ b/cmd/cli/instance.go @@ -13,6 +13,7 @@ import ( "github.com/docker/infrakit/pkg/plugin" instance_plugin "github.com/docker/infrakit/pkg/rpc/instance" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/spf13/cobra" ) @@ -57,7 +58,7 @@ func instancePluginCommand(plugins func() discovery.Plugins) *cobra.Command { os.Exit(1) } - err = instancePlugin.Validate(json.RawMessage(buff)) + err = instancePlugin.Validate(types.AnyBytes(buff)) if err == nil { fmt.Println("validate:ok") } diff --git a/pkg/mock/spi/flavor/flavor.go b/pkg/mock/spi/flavor/flavor.go index f566b94b5..2a4a28131 100644 --- a/pkg/mock/spi/flavor/flavor.go +++ b/pkg/mock/spi/flavor/flavor.go @@ -4,10 +4,10 @@ package flavor import ( - json "encoding/json" types "github.com/docker/infrakit/pkg/plugin/group/types" flavor "github.com/docker/infrakit/pkg/spi/flavor" instance "github.com/docker/infrakit/pkg/spi/instance" + types0 "github.com/docker/infrakit/pkg/types" gomock "github.com/golang/mock/gomock" ) @@ -32,7 +32,7 @@ func (_m *MockPlugin) EXPECT() *_MockPluginRecorder { return _m.recorder } -func (_m *MockPlugin) Drain(_param0 json.RawMessage, _param1 instance.Description) error { +func (_m *MockPlugin) Drain(_param0 *types0.Any, _param1 instance.Description) error { ret := _m.ctrl.Call(_m, "Drain", _param0, _param1) ret0, _ := ret[0].(error) return ret0 @@ -42,7 +42,7 @@ func (_mr *_MockPluginRecorder) Drain(arg0, arg1 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Drain", arg0, arg1) } -func (_m *MockPlugin) Healthy(_param0 json.RawMessage, _param1 instance.Description) (flavor.Health, error) { +func (_m *MockPlugin) Healthy(_param0 *types0.Any, _param1 instance.Description) (flavor.Health, error) { ret := _m.ctrl.Call(_m, "Healthy", _param0, _param1) ret0, _ := ret[0].(flavor.Health) ret1, _ := ret[1].(error) @@ -53,7 +53,7 @@ func (_mr *_MockPluginRecorder) Healthy(arg0, arg1 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Healthy", arg0, arg1) } -func (_m *MockPlugin) Prepare(_param0 json.RawMessage, _param1 instance.Spec, _param2 types.AllocationMethod) (instance.Spec, error) { +func (_m *MockPlugin) Prepare(_param0 *types0.Any, _param1 instance.Spec, _param2 types.AllocationMethod) (instance.Spec, error) { ret := _m.ctrl.Call(_m, "Prepare", _param0, _param1, _param2) ret0, _ := ret[0].(instance.Spec) ret1, _ := ret[1].(error) @@ -64,7 +64,7 @@ func (_mr *_MockPluginRecorder) Prepare(arg0, arg1, arg2 interface{}) *gomock.Ca return _mr.mock.ctrl.RecordCall(_mr.mock, "Prepare", arg0, arg1, arg2) } -func (_m *MockPlugin) Validate(_param0 json.RawMessage, _param1 types.AllocationMethod) error { +func (_m *MockPlugin) Validate(_param0 *types0.Any, _param1 types.AllocationMethod) error { ret := _m.ctrl.Call(_m, "Validate", _param0, _param1) ret0, _ := ret[0].(error) return ret0 diff --git a/pkg/mock/spi/instance/instance.go b/pkg/mock/spi/instance/instance.go index ff2e8f249..400b3291a 100644 --- a/pkg/mock/spi/instance/instance.go +++ b/pkg/mock/spi/instance/instance.go @@ -4,8 +4,8 @@ package instance import ( - json "encoding/json" instance "github.com/docker/infrakit/pkg/spi/instance" + types "github.com/docker/infrakit/pkg/types" gomock "github.com/golang/mock/gomock" ) @@ -51,6 +51,16 @@ func (_mr *_MockPluginRecorder) Destroy(arg0 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Destroy", arg0) } +func (_m *MockPlugin) Label(_param0 instance.ID, _param1 map[string]string) error { + ret := _m.ctrl.Call(_m, "Label", _param0, _param1) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockPluginRecorder) Label(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Label", arg0, arg1) +} + func (_m *MockPlugin) Provision(_param0 instance.Spec) (*instance.ID, error) { ret := _m.ctrl.Call(_m, "Provision", _param0) ret0, _ := ret[0].(*instance.ID) @@ -62,7 +72,7 @@ func (_mr *_MockPluginRecorder) Provision(arg0 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Provision", arg0) } -func (_m *MockPlugin) Validate(_param0 json.RawMessage) error { +func (_m *MockPlugin) Validate(_param0 *types.Any) error { ret := _m.ctrl.Call(_m, "Validate", _param0) ret0, _ := ret[0].(error) return ret0 diff --git a/pkg/plugin/group/group.go b/pkg/plugin/group/group.go index 0807dc6f7..38a4b1b3f 100644 --- a/pkg/plugin/group/group.go +++ b/pkg/plugin/group/group.go @@ -8,7 +8,7 @@ import ( log "github.com/Sirupsen/logrus" plugin_base "github.com/docker/infrakit/pkg/plugin" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/spi/instance" @@ -180,7 +180,7 @@ func (p *plugin) InspectGroups() ([]group.Spec, error) { var specs []group.Spec err := p.groups.forEach(func(id group.ID, ctx *groupContext) error { if ctx != nil { - spec, err := types.UnparseProperties(string(id), ctx.settings.config) + spec, err := group_types.UnparseProperties(string(id), ctx.settings.config) if err != nil { return err } @@ -219,7 +219,7 @@ func (p *plugin) validate(config group.Spec) (groupSettings, error) { return noSettings, errors.New("Group ID must not be blank") } - parsed, err := types.ParseProperties(config) + parsed, err := group_types.ParseProperties(config) if err != nil { return noSettings, err } @@ -240,7 +240,7 @@ func (p *plugin) validate(config group.Spec) (groupSettings, error) { return noSettings, fmt.Errorf("Failed to find Flavor plugin '%s':%v", parsed.Flavor.Plugin, err) } - if err := flavorPlugin.Validate(types.RawMessage(parsed.Flavor.Properties), parsed.Allocation); err != nil { + if err := flavorPlugin.Validate(parsed.Flavor.Properties, parsed.Allocation); err != nil { return noSettings, err } @@ -249,7 +249,7 @@ func (p *plugin) validate(config group.Spec) (groupSettings, error) { return noSettings, fmt.Errorf("Failed to find Instance plugin '%s':%v", parsed.Instance.Plugin, err) } - if err := instancePlugin.Validate(types.RawMessage(parsed.Instance.Properties)); err != nil { + if err := instancePlugin.Validate(parsed.Instance.Properties); err != nil { return noSettings, err } diff --git a/pkg/plugin/group/integration_test.go b/pkg/plugin/group/integration_test.go index 2d025fa52..d1f4f8d34 100644 --- a/pkg/plugin/group/integration_test.go +++ b/pkg/plugin/group/integration_test.go @@ -177,8 +177,8 @@ func TestRollingUpdate(t *testing.T) { ) flavorPlugin := testFlavor{ - healthy: func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { - if strings.Contains(string(flavorProperties), "flavor2") { + healthy: func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { + if strings.Contains(flavorProperties.String(), "flavor2") { return flavor.Healthy, nil } @@ -480,8 +480,8 @@ func TestFreeGroupWhileConverging(t *testing.T) { healthChecksStarted := make(chan bool) defer close(healthChecksStarted) flavorPlugin := testFlavor{ - healthy: func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { - if strings.Contains(string(flavorProperties), "flavor2") { + healthy: func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { + if strings.Contains(flavorProperties.String(), "flavor2") { // sync.Once is used to prevent writing to healthChecksStarted more than one time, // causing the test to deadlock. once.Do(func() { @@ -527,8 +527,8 @@ func TestUpdateFailsWhenInstanceIsUnhealthy(t *testing.T) { ) flavorPlugin := testFlavor{ - healthy: func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { - if strings.Contains(string(flavorProperties), "bad update") { + healthy: func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { + if strings.Contains(flavorProperties.String(), "bad update") { return flavor.Unhealthy, nil } return flavor.Healthy, nil diff --git a/pkg/plugin/group/scaled.go b/pkg/plugin/group/scaled.go index 368196cfc..d1047d7e1 100644 --- a/pkg/plugin/group/scaled.go +++ b/pkg/plugin/group/scaled.go @@ -2,10 +2,11 @@ package group import ( "fmt" + log "github.com/Sirupsen/logrus" - "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "sync" ) @@ -61,11 +62,10 @@ func (s *scaledGroup) CreateOne(logicalID *instance.LogicalID) { spec := instance.Spec{ Tags: tags, LogicalID: logicalID, - Properties: types.RawMessagePtr(settings.config.Instance.Properties), + Properties: types.AnyCopy(settings.config.Instance.Properties), } - spec, err := settings.flavorPlugin.Prepare( - types.RawMessage(settings.config.Flavor.Properties), + spec, err := settings.flavorPlugin.Prepare(types.AnyCopy(settings.config.Flavor.Properties), spec, settings.config.Allocation) if err != nil { @@ -90,9 +90,7 @@ func (s *scaledGroup) CreateOne(logicalID *instance.LogicalID) { func (s *scaledGroup) Health(inst instance.Description) flavor.Health { settings := s.latestSettings() - health, err := settings.flavorPlugin.Healthy( - types.RawMessage(settings.config.Flavor.Properties), - inst) + health, err := settings.flavorPlugin.Healthy(types.AnyCopy(settings.config.Flavor.Properties), inst) if err != nil { log.Warnf("Failed to check health of instance %s: %s", inst.ID, err) return flavor.Unknown @@ -104,7 +102,7 @@ func (s *scaledGroup) Health(inst instance.Description) flavor.Health { func (s *scaledGroup) Destroy(inst instance.Description) { settings := s.latestSettings() - flavorProperties := types.RawMessage(settings.config.Flavor.Properties) + flavorProperties := types.AnyCopy(settings.config.Flavor.Properties) if err := settings.flavorPlugin.Drain(flavorProperties, inst); err != nil { log.Errorf("Failed to drain %s: %s", inst.ID, err) } diff --git a/pkg/plugin/group/testplugin.go b/pkg/plugin/group/testplugin.go index 2dc72408b..8ce70e57a 100644 --- a/pkg/plugin/group/testplugin.go +++ b/pkg/plugin/group/testplugin.go @@ -1,14 +1,15 @@ package group import ( - "encoding/json" "errors" "fmt" - "github.com/docker/infrakit/pkg/plugin/group/types" + "sync" + + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/plugin/group/util" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" - "sync" + "github.com/docker/infrakit/pkg/types" ) // newTestInstancePlugin creates a new instance plugin for use in testing and development. @@ -39,7 +40,7 @@ func (d *testplugin) instancesCopy() map[instance.ID]instance.Spec { return instances } -func (d *testplugin) Validate(req json.RawMessage) error { +func (d *testplugin) Validate(req *types.Any) error { return nil } @@ -59,6 +60,10 @@ func (d *testplugin) Provision(spec instance.Spec) (*instance.ID, error) { return &id, nil } +func (d *testplugin) Label(id instance.ID, labels map[string]string) error { + return nil +} + func (d *testplugin) Destroy(id instance.ID) error { d.lock.Lock() defer d.lock.Unlock() @@ -103,8 +108,8 @@ const ( ) type testFlavor struct { - healthy func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) - drain func(flavorProperties json.RawMessage, inst instance.Description) error + healthy func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) + drain func(flavorProperties *types.Any, inst instance.Description) error } type flavorSchema struct { @@ -113,10 +118,10 @@ type flavorSchema struct { Tags map[string]string } -func (t testFlavor) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (t testFlavor) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { s := flavorSchema{} - err := json.Unmarshal(flavorProperties, &s) + err := flavorProperties.Decode(&s) if err != nil { return err } @@ -137,13 +142,12 @@ func (t testFlavor) Validate(flavorProperties json.RawMessage, allocation types. } } -func (t testFlavor) Prepare( - flavorProperties json.RawMessage, +func (t testFlavor) Prepare(flavorProperties *types.Any, spec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { s := flavorSchema{} - err := json.Unmarshal(flavorProperties, &s) + err := flavorProperties.Decode(&s) if err != nil { return spec, err } @@ -155,7 +159,7 @@ func (t testFlavor) Prepare( return spec, nil } -func (t testFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (t testFlavor) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { if t.healthy != nil { return t.healthy(flavorProperties, inst) } @@ -163,7 +167,7 @@ func (t testFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Desc return flavor.Healthy, nil } -func (t testFlavor) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (t testFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error { if t.drain != nil { return t.drain(flavorProperties, inst) } diff --git a/pkg/plugin/group/types/types.go b/pkg/plugin/group/types/types.go index 1997dea08..6269a7bfd 100644 --- a/pkg/plugin/group/types/types.go +++ b/pkg/plugin/group/types/types.go @@ -100,23 +100,3 @@ func (c Spec) InstanceHash() string { hasher.Write(stableFormat(c.Flavor)) return base64.URLEncoding.EncodeToString(hasher.Sum(nil)) } - -// RawMessage converts a pointer to a raw message to a copy of the value. If the pointer is nil, it returns -// an empty raw message. This is useful for structs where fields are json.RawMessage pointers for bi-directional -// marshal and unmarshal (value receivers will encode base64 instead of raw json when marshaled), so bi-directional -// structs should use pointer fields. -func RawMessage(r *types.Any) (raw json.RawMessage) { - if r != nil { - raw = json.RawMessage(r.Bytes()) - } - return -} - -// RawMessagePtr makes a copy of the Any and make it available as a pointer to a JSON raw message -func RawMessagePtr(r *types.Any) *json.RawMessage { - if r == nil { - return nil - } - raw := json.RawMessage(r.Bytes()) - return &raw -} diff --git a/pkg/rpc/flavor/client.go b/pkg/rpc/flavor/client.go index 6253d5986..65b57b093 100644 --- a/pkg/rpc/flavor/client.go +++ b/pkg/rpc/flavor/client.go @@ -1,13 +1,12 @@ package flavor import ( - "encoding/json" - "github.com/docker/infrakit/pkg/plugin" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" rpc_client "github.com/docker/infrakit/pkg/rpc/client" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) // NewClient returns a plugin interface implementation connected to a remote plugin @@ -25,9 +24,9 @@ type client struct { } // Validate checks whether the helper can support a configuration. -func (c client) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (c client) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { _, flavorType := c.name.GetLookupAndType() - req := ValidateRequest{Type: flavorType, Properties: &flavorProperties, Allocation: allocation} + req := ValidateRequest{Type: flavorType, Properties: flavorProperties, Allocation: allocation} resp := ValidateResponse{} return c.client.Call("Flavor.Validate", req, &resp) } @@ -35,9 +34,11 @@ func (c client) Validate(flavorProperties json.RawMessage, allocation types.Allo // Prepare allows the Flavor to modify the provisioning instructions for an instance. For example, a // helper could be used to place additional tags on the machine, or generate a specialized Init command based on // the flavor configuration. -func (c client) Prepare(flavorProperties json.RawMessage, spec instance.Spec, allocation types.AllocationMethod) (instance.Spec, error) { +func (c client) Prepare(flavorProperties *types.Any, + spec instance.Spec, allocation group_types.AllocationMethod) (instance.Spec, error) { + _, flavorType := c.name.GetLookupAndType() - req := PrepareRequest{Type: flavorType, Properties: &flavorProperties, Spec: spec, Allocation: allocation} + req := PrepareRequest{Type: flavorType, Properties: flavorProperties, Spec: spec, Allocation: allocation} resp := PrepareResponse{} err := c.client.Call("Flavor.Prepare", req, &resp) if err != nil { @@ -47,18 +48,20 @@ func (c client) Prepare(flavorProperties json.RawMessage, spec instance.Spec, al } // Healthy determines the Health of this Flavor on an instance. -func (c client) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (c client) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { + _, flavorType := c.name.GetLookupAndType() - req := HealthyRequest{Type: flavorType, Properties: &flavorProperties, Instance: inst} + req := HealthyRequest{Type: flavorType, Properties: flavorProperties, Instance: inst} resp := HealthyResponse{} err := c.client.Call("Flavor.Healthy", req, &resp) return resp.Health, err } // Drain allows the flavor to perform a best-effort cleanup operation before the instance is destroyed. -func (c client) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (c client) Drain(flavorProperties *types.Any, inst instance.Description) error { + _, flavorType := c.name.GetLookupAndType() - req := DrainRequest{Type: flavorType, Properties: &flavorProperties, Instance: inst} + req := DrainRequest{Type: flavorType, Properties: flavorProperties, Instance: inst} resp := DrainResponse{} err := c.client.Call("Flavor.Drain", req, &resp) if err != nil { diff --git a/pkg/rpc/flavor/rpc_multi_test.go b/pkg/rpc/flavor/rpc_multi_test.go index 61d96e3d8..8d7a72e1a 100644 --- a/pkg/rpc/flavor/rpc_multi_test.go +++ b/pkg/rpc/flavor/rpc_multi_test.go @@ -1,16 +1,16 @@ package flavor import ( - "encoding/json" "errors" "path/filepath" "testing" "github.com/docker/infrakit/pkg/plugin" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" rpc_server "github.com/docker/infrakit/pkg/rpc/server" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/stretchr/testify/require" ) @@ -25,22 +25,22 @@ func TestFlavorMultiPluginValidate(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputFlavorPropertiesActual1 := make(chan json.RawMessage, 1) - inputFlavorProperties1 := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"leader"}`)) + inputFlavorPropertiesActual1 := make(chan *types.Any, 1) + inputFlavorProperties1 := types.AnyString(`{"flavor":"zookeeper","role":"leader"}`) - inputFlavorPropertiesActual2 := make(chan json.RawMessage, 1) - inputFlavorProperties2 := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"follower"}`)) + inputFlavorPropertiesActual2 := make(chan *types.Any, 1) + inputFlavorProperties2 := types.AnyString(`{"flavor":"zookeeper","role":"follower"}`) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes( map[string]flavor.Plugin{ "type1": &testPlugin{ - DoValidate: func(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { + DoValidate: func(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { inputFlavorPropertiesActual1 <- flavorProperties return nil }, }, "type2": &testPlugin{ - DoValidate: func(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { + DoValidate: func(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { inputFlavorPropertiesActual2 <- flavorProperties return errors.New("something-went-wrong") }, @@ -64,19 +64,19 @@ func TestFlavorMultiPluginPrepare(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputFlavorPropertiesActual1 := make(chan json.RawMessage, 1) - inputFlavorProperties1 := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"leader"}`)) + inputFlavorPropertiesActual1 := make(chan *types.Any, 1) + inputFlavorProperties1 := types.AnyString(`{"flavor":"zookeeper","role":"leader"}`) inputInstanceSpecActual1 := make(chan instance.Spec, 1) inputInstanceSpec1 := instance.Spec{ - Properties: &inputFlavorProperties1, + Properties: inputFlavorProperties1, Tags: map[string]string{"foo": "bar1"}, } - inputFlavorPropertiesActual2 := make(chan json.RawMessage, 1) - inputFlavorProperties2 := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"follower"}`)) + inputFlavorPropertiesActual2 := make(chan *types.Any, 1) + inputFlavorProperties2 := types.AnyString(`{"flavor":"zookeeper","role":"follower"}`) inputInstanceSpecActual2 := make(chan instance.Spec, 1) inputInstanceSpec2 := instance.Spec{ - Properties: &inputFlavorProperties2, + Properties: inputFlavorProperties2, Tags: map[string]string{"foo": "bar2"}, } @@ -84,9 +84,9 @@ func TestFlavorMultiPluginPrepare(t *testing.T) { map[string]flavor.Plugin{ "type1": &testPlugin{ DoPrepare: func( - flavorProperties json.RawMessage, + flavorProperties *types.Any, instanceSpec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { inputFlavorPropertiesActual1 <- flavorProperties inputInstanceSpecActual1 <- instanceSpec @@ -96,9 +96,9 @@ func TestFlavorMultiPluginPrepare(t *testing.T) { }, "type2": &testPlugin{ DoPrepare: func( - flavorProperties json.RawMessage, + flavorProperties *types.Any, instanceSpec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { inputFlavorPropertiesActual2 <- flavorProperties inputInstanceSpecActual2 <- instanceSpec @@ -137,17 +137,17 @@ func TestFlavorMultiPluginHealthy(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputPropertiesActual1 := make(chan json.RawMessage, 1) + inputPropertiesActual1 := make(chan *types.Any, 1) inputInstanceActual1 := make(chan instance.Description, 1) - inputProperties1 := json.RawMessage("{}") + inputProperties1 := types.AnyString("{}") inputInstance1 := instance.Description{ ID: instance.ID("foo1"), Tags: map[string]string{"foo": "bar1"}, } - inputPropertiesActual2 := make(chan json.RawMessage, 1) + inputPropertiesActual2 := make(chan *types.Any, 1) inputInstanceActual2 := make(chan instance.Description, 1) - inputProperties2 := json.RawMessage("{}") + inputProperties2 := types.AnyString("{}") inputInstance2 := instance.Description{ ID: instance.ID("foo2"), Tags: map[string]string{"foo": "bar2"}, @@ -156,14 +156,14 @@ func TestFlavorMultiPluginHealthy(t *testing.T) { server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes( map[string]flavor.Plugin{ "type1": &testPlugin{ - DoHealthy: func(properties json.RawMessage, inst instance.Description) (flavor.Health, error) { + DoHealthy: func(properties *types.Any, inst instance.Description) (flavor.Health, error) { inputPropertiesActual1 <- properties inputInstanceActual1 <- inst return flavor.Healthy, nil }, }, "type2": &testPlugin{ - DoHealthy: func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { + DoHealthy: func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { inputPropertiesActual2 <- flavorProperties inputInstanceActual2 <- inst return flavor.Unknown, errors.New("oh-noes") @@ -193,17 +193,17 @@ func TestFlavorMultiPluginDrain(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputPropertiesActual1 := make(chan json.RawMessage, 1) + inputPropertiesActual1 := make(chan *types.Any, 1) inputInstanceActual1 := make(chan instance.Description, 1) - inputProperties1 := json.RawMessage("{}") + inputProperties1 := types.AnyString("{}") inputInstance1 := instance.Description{ ID: instance.ID("foo1"), Tags: map[string]string{"foo": "bar1"}, } - inputPropertiesActual2 := make(chan json.RawMessage, 1) + inputPropertiesActual2 := make(chan *types.Any, 1) inputInstanceActual2 := make(chan instance.Description, 1) - inputProperties2 := json.RawMessage("{}") + inputProperties2 := types.AnyString("{}") inputInstance2 := instance.Description{ ID: instance.ID("foo2"), Tags: map[string]string{"foo": "bar2"}, @@ -212,14 +212,14 @@ func TestFlavorMultiPluginDrain(t *testing.T) { server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes( map[string]flavor.Plugin{ "type1": &testPlugin{ - DoDrain: func(properties json.RawMessage, inst instance.Description) error { + DoDrain: func(properties *types.Any, inst instance.Description) error { inputPropertiesActual1 <- properties inputInstanceActual1 <- inst return nil }, }, "type2": &testPlugin{ - DoDrain: func(flavorProperties json.RawMessage, inst instance.Description) error { + DoDrain: func(flavorProperties *types.Any, inst instance.Description) error { inputPropertiesActual2 <- flavorProperties inputInstanceActual2 <- inst return errors.New("oh-noes") diff --git a/pkg/rpc/flavor/rpc_test.go b/pkg/rpc/flavor/rpc_test.go index e2d80c347..343fa6604 100644 --- a/pkg/rpc/flavor/rpc_test.go +++ b/pkg/rpc/flavor/rpc_test.go @@ -1,22 +1,22 @@ package flavor import ( - "encoding/json" "errors" "path/filepath" "testing" "github.com/docker/infrakit/pkg/plugin" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" rpc_server "github.com/docker/infrakit/pkg/rpc/server" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/stretchr/testify/require" "io/ioutil" "path" ) -var allocation = types.AllocationMethod{} +var allocation = group_types.AllocationMethod{} func tempSocket() string { dir, err := ioutil.TempDir("", "infrakit-test-") @@ -28,32 +28,29 @@ func tempSocket() string { } type testPlugin struct { - DoValidate func(flavorProperties json.RawMessage, allocation types.AllocationMethod) error - DoPrepare func( - flavorProperties json.RawMessage, - spec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) - DoHealthy func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) - DoDrain func(flavorProperties json.RawMessage, inst instance.Description) error + DoValidate func(flavorProperties *types.Any, allocation group_types.AllocationMethod) error + DoPrepare func(flavorProperties *types.Any, spec instance.Spec, + allocation group_types.AllocationMethod) (instance.Spec, error) + DoHealthy func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) + DoDrain func(flavorProperties *types.Any, inst instance.Description) error } -func (t *testPlugin) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (t *testPlugin) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { return t.DoValidate(flavorProperties, allocation) } -func (t *testPlugin) Prepare( - flavorProperties json.RawMessage, +func (t *testPlugin) Prepare(flavorProperties *types.Any, spec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { return t.DoPrepare(flavorProperties, spec, allocation) } -func (t *testPlugin) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (t *testPlugin) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { return t.DoHealthy(flavorProperties, inst) } -func (t *testPlugin) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (t *testPlugin) Drain(flavorProperties *types.Any, inst instance.Description) error { return t.DoDrain(flavorProperties, inst) } @@ -61,11 +58,11 @@ func TestFlavorPluginValidate(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputFlavorPropertiesActual := make(chan json.RawMessage, 1) - inputFlavorProperties := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"leader"}`)) + inputFlavorPropertiesActual := make(chan *types.Any, 1) + inputFlavorProperties := types.AnyString(`{"flavor":"zookeeper","role":"leader"}`) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoValidate: func(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { + DoValidate: func(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { inputFlavorPropertiesActual <- flavorProperties return nil }, @@ -83,11 +80,11 @@ func TestFlavorPluginValidateError(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputFlavorPropertiesActual := make(chan json.RawMessage, 1) - inputFlavorProperties := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"leader"}`)) + inputFlavorPropertiesActual := make(chan *types.Any, 1) + inputFlavorProperties := types.AnyString(`{"flavor":"zookeeper","role":"leader"}`) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoValidate: func(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { + DoValidate: func(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { inputFlavorPropertiesActual <- flavorProperties return errors.New("something-went-wrong") }, @@ -106,19 +103,19 @@ func TestFlavorPluginPrepare(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputFlavorPropertiesActual := make(chan json.RawMessage, 1) - inputFlavorProperties := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"leader"}`)) + inputFlavorPropertiesActual := make(chan *types.Any, 1) + inputFlavorProperties := types.AnyString(`{"flavor":"zookeeper","role":"leader"}`) inputInstanceSpecActual := make(chan instance.Spec, 1) inputInstanceSpec := instance.Spec{ - Properties: &inputFlavorProperties, + Properties: inputFlavorProperties, Tags: map[string]string{"foo": "bar"}, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ DoPrepare: func( - flavorProperties json.RawMessage, + flavorProperties *types.Any, instanceSpec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { inputFlavorPropertiesActual <- flavorProperties inputInstanceSpecActual <- instanceSpec @@ -145,19 +142,19 @@ func TestFlavorPluginPrepareError(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputFlavorPropertiesActual := make(chan json.RawMessage, 1) - inputFlavorProperties := json.RawMessage([]byte(`{"flavor":"zookeeper","role":"leader"}`)) + inputFlavorPropertiesActual := make(chan *types.Any, 1) + inputFlavorProperties := types.AnyString(`{"flavor":"zookeeper","role":"leader"}`) inputInstanceSpecActual := make(chan instance.Spec, 1) inputInstanceSpec := instance.Spec{ - Properties: &inputFlavorProperties, + Properties: inputFlavorProperties, Tags: map[string]string{"foo": "bar"}, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ DoPrepare: func( - flavorProperties json.RawMessage, + flavorProperties *types.Any, instanceSpec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { inputFlavorPropertiesActual <- flavorProperties inputInstanceSpecActual <- instanceSpec @@ -184,15 +181,15 @@ func TestFlavorPluginHealthy(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputPropertiesActual := make(chan json.RawMessage, 1) + inputPropertiesActual := make(chan *types.Any, 1) inputInstanceActual := make(chan instance.Description, 1) - inputProperties := json.RawMessage("{}") + inputProperties := types.AnyString("{}") inputInstance := instance.Description{ ID: instance.ID("foo"), Tags: map[string]string{"foo": "bar"}, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoHealthy: func(properties json.RawMessage, inst instance.Description) (flavor.Health, error) { + DoHealthy: func(properties *types.Any, inst instance.Description) (flavor.Health, error) { inputPropertiesActual <- properties inputInstanceActual <- inst return flavor.Healthy, nil @@ -213,15 +210,15 @@ func TestFlavorPluginHealthyError(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputPropertiesActual := make(chan json.RawMessage, 1) + inputPropertiesActual := make(chan *types.Any, 1) inputInstanceActual := make(chan instance.Description, 1) - inputProperties := json.RawMessage("{}") + inputProperties := types.AnyString("{}") inputInstance := instance.Description{ ID: instance.ID("foo"), Tags: map[string]string{"foo": "bar"}, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoHealthy: func(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { + DoHealthy: func(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { inputPropertiesActual <- flavorProperties inputInstanceActual <- inst return flavor.Unknown, errors.New("oh-noes") @@ -242,15 +239,15 @@ func TestFlavorPluginDrain(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputPropertiesActual := make(chan json.RawMessage, 1) + inputPropertiesActual := make(chan *types.Any, 1) inputInstanceActual := make(chan instance.Description, 1) - inputProperties := json.RawMessage("{}") + inputProperties := types.AnyString("{}") inputInstance := instance.Description{ ID: instance.ID("foo"), Tags: map[string]string{"foo": "bar"}, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoDrain: func(properties json.RawMessage, inst instance.Description) error { + DoDrain: func(properties *types.Any, inst instance.Description) error { inputPropertiesActual <- properties inputInstanceActual <- inst return nil @@ -269,15 +266,15 @@ func TestFlavorPluginDrainError(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - inputPropertiesActual := make(chan json.RawMessage, 1) + inputPropertiesActual := make(chan *types.Any, 1) inputInstanceActual := make(chan instance.Description, 1) - inputProperties := json.RawMessage("{}") + inputProperties := types.AnyString("{}") inputInstance := instance.Description{ ID: instance.ID("foo"), Tags: map[string]string{"foo": "bar"}, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoDrain: func(flavorProperties json.RawMessage, inst instance.Description) error { + DoDrain: func(flavorProperties *types.Any, inst instance.Description) error { inputPropertiesActual <- flavorProperties inputInstanceActual <- inst return errors.New("oh-noes") diff --git a/pkg/rpc/flavor/service.go b/pkg/rpc/flavor/service.go index 1977592d9..128e445ed 100644 --- a/pkg/rpc/flavor/service.go +++ b/pkg/rpc/flavor/service.go @@ -1,12 +1,12 @@ package flavor import ( - "encoding/json" "fmt" "net/http" "github.com/docker/infrakit/pkg/spi" "github.com/docker/infrakit/pkg/spi/flavor" + "github.com/docker/infrakit/pkg/types" ) // PluginServer returns a Flavor that conforms to the net/rpc rpc call convention. @@ -66,7 +66,7 @@ func (p *Flavor) SetExampleProperties(request interface{}) { } // exampleProperties returns an example properties used by the plugin -func (p *Flavor) exampleProperties() *json.RawMessage { +func (p *Flavor) exampleProperties() *types.Any { if i, is := p.plugin.(spi.InputExample); is { return i.ExampleProperties() } @@ -90,17 +90,12 @@ func (p *Flavor) getPlugin(flavorType string) flavor.Plugin { // Validate checks whether the helper can support a configuration. func (p *Flavor) Validate(_ *http.Request, req *ValidateRequest, resp *ValidateResponse) error { - var raw json.RawMessage - if req.Properties != nil { - raw = *req.Properties - } - resp.Type = req.Type c := p.getPlugin(req.Type) if c == nil { return fmt.Errorf("no-plugin:%s", req.Type) } - err := c.Validate(raw, req.Allocation) + err := c.Validate(req.Properties, req.Allocation) if err != nil { return err } @@ -112,17 +107,12 @@ func (p *Flavor) Validate(_ *http.Request, req *ValidateRequest, resp *ValidateR // helper could be used to place additional tags on the machine, or generate a specialized Init command based on // the flavor configuration. func (p *Flavor) Prepare(_ *http.Request, req *PrepareRequest, resp *PrepareResponse) error { - var raw json.RawMessage - if req.Properties != nil { - raw = *req.Properties - } - resp.Type = req.Type c := p.getPlugin(req.Type) if c == nil { return fmt.Errorf("no-plugin:%s", req.Type) } - spec, err := c.Prepare(raw, req.Spec, req.Allocation) + spec, err := c.Prepare(req.Properties, req.Spec, req.Allocation) if err != nil { return err } @@ -137,7 +127,7 @@ func (p *Flavor) Healthy(_ *http.Request, req *HealthyRequest, resp *HealthyResp if c == nil { return fmt.Errorf("no-plugin:%s", req.Type) } - health, err := c.Healthy(*req.Properties, req.Instance) + health, err := c.Healthy(req.Properties, req.Instance) if err != nil { return err } @@ -152,7 +142,7 @@ func (p *Flavor) Drain(_ *http.Request, req *DrainRequest, resp *DrainResponse) if c == nil { return fmt.Errorf("no-plugin:%s", req.Type) } - err := c.Drain(*req.Properties, req.Instance) + err := c.Drain(req.Properties, req.Instance) if err != nil { return err } diff --git a/pkg/rpc/flavor/types.go b/pkg/rpc/flavor/types.go index 1c46efc76..1450977e0 100644 --- a/pkg/rpc/flavor/types.go +++ b/pkg/rpc/flavor/types.go @@ -1,18 +1,17 @@ package flavor import ( - "encoding/json" - - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) // ValidateRequest is the rpc wrapper for request parameters to Validate type ValidateRequest struct { Type string - Properties *json.RawMessage - Allocation types.AllocationMethod + Properties *types.Any + Allocation group_types.AllocationMethod } // ValidateResponse is the rpc wrapper for the results of Validate @@ -24,9 +23,9 @@ type ValidateResponse struct { // PrepareRequest is the rpc wrapper of the params to Prepare type PrepareRequest struct { Type string - Properties *json.RawMessage + Properties *types.Any Spec instance.Spec - Allocation types.AllocationMethod + Allocation group_types.AllocationMethod } // PrepareResponse is the rpc wrapper of the result of Prepare @@ -38,7 +37,7 @@ type PrepareResponse struct { // HealthyRequest is the rpc wrapper of the params to Healthy type HealthyRequest struct { Type string - Properties *json.RawMessage + Properties *types.Any Instance instance.Description } @@ -51,7 +50,7 @@ type HealthyResponse struct { // DrainRequest is the rpc wrapper of the params to Drain type DrainRequest struct { Type string - Properties *json.RawMessage + Properties *types.Any Instance instance.Description } diff --git a/pkg/rpc/group/service.go b/pkg/rpc/group/service.go index 502c3e645..52baee4ed 100644 --- a/pkg/rpc/group/service.go +++ b/pkg/rpc/group/service.go @@ -1,11 +1,11 @@ package group import ( - "encoding/json" "net/http" "github.com/docker/infrakit/pkg/spi" "github.com/docker/infrakit/pkg/spi/group" + "github.com/docker/infrakit/pkg/types" ) // PluginServer returns a RPCService that conforms to the net/rpc rpc call convention. @@ -27,7 +27,7 @@ func (p *Group) VendorInfo() *spi.VendorInfo { } // ExampleProperties returns an example properties used by the plugin -func (p *Group) ExampleProperties() *json.RawMessage { +func (p *Group) ExampleProperties() *types.Any { if i, is := p.plugin.(spi.InputExample); is { return i.ExampleProperties() } diff --git a/pkg/rpc/instance/client.go b/pkg/rpc/instance/client.go index 3e4ceb97a..b8fb9134a 100644 --- a/pkg/rpc/instance/client.go +++ b/pkg/rpc/instance/client.go @@ -1,11 +1,10 @@ package instance import ( - "encoding/json" - "github.com/docker/infrakit/pkg/plugin" rpc_client "github.com/docker/infrakit/pkg/rpc/client" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) // NewClient returns a plugin interface implementation connected to a plugin @@ -23,9 +22,9 @@ type client struct { } // Validate performs local validation on a provision request. -func (c client) Validate(properties json.RawMessage) error { +func (c client) Validate(properties *types.Any) error { _, instanceType := c.name.GetLookupAndType() - req := ValidateRequest{Properties: &properties, Type: instanceType} + req := ValidateRequest{Properties: properties, Type: instanceType} resp := ValidateResponse{} return c.client.Call("Instance.Validate", req, &resp) @@ -44,6 +43,19 @@ func (c client) Provision(spec instance.Spec) (*instance.ID, error) { return resp.ID, nil } +// Label labels the instance +func (c client) Label(instance instance.ID, labels map[string]string) error { + _, instanceType := c.name.GetLookupAndType() + req := LabelRequest{Type: instanceType, Instance: instance} + resp := LabelResponse{} + + if err := c.client.Call("Instance.Label", req, &resp); err != nil { + return err + } + + return nil +} + // Destroy terminates an existing instance. func (c client) Destroy(instance instance.ID) error { _, instanceType := c.name.GetLookupAndType() diff --git a/pkg/rpc/instance/rpc_multi_test.go b/pkg/rpc/instance/rpc_multi_test.go index 53a9edf3f..07639a6aa 100644 --- a/pkg/rpc/instance/rpc_multi_test.go +++ b/pkg/rpc/instance/rpc_multi_test.go @@ -1,7 +1,6 @@ package instance import ( - "encoding/json" "errors" "path/filepath" "testing" @@ -9,6 +8,7 @@ import ( "github.com/docker/infrakit/pkg/plugin" rpc_server "github.com/docker/infrakit/pkg/rpc/server" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/stretchr/testify/require" ) @@ -23,16 +23,16 @@ func TestInstanceTypedPluginValidate(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - raw1 := json.RawMessage([]byte(`{"name":"instance","type":"xlarge1"}`)) - raw2 := json.RawMessage([]byte(`{"name":"instance","type":"xlarge2"}`)) + raw1 := types.AnyString(`{"name":"instance","type":"xlarge1"}`) + raw2 := types.AnyString(`{"name":"instance","type":"xlarge2"}`) - rawActual1 := make(chan json.RawMessage, 1) - rawActual2 := make(chan json.RawMessage, 1) + rawActual1 := make(chan *types.Any, 1) + rawActual2 := make(chan *types.Any, 1) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes( map[string]instance.Plugin{ "type1": &testPlugin{ - DoValidate: func(req json.RawMessage) error { + DoValidate: func(req *types.Any) error { rawActual1 <- req @@ -40,7 +40,7 @@ func TestInstanceTypedPluginValidate(t *testing.T) { }, }, "type2": &testPlugin{ - DoValidate: func(req json.RawMessage) error { + DoValidate: func(req *types.Any) error { rawActual2 <- req @@ -71,14 +71,14 @@ func TestInstanceTypedPluginValidateError(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - raw1 := json.RawMessage([]byte(`{"name":"instance","type":"xlarge1"}`)) - rawActual1 := make(chan json.RawMessage, 1) - raw2 := json.RawMessage([]byte(`{"name":"instance","type":"xlarge2"}`)) - rawActual2 := make(chan json.RawMessage, 1) + raw1 := types.AnyString(`{"name":"instance","type":"xlarge1"}`) + rawActual1 := make(chan *types.Any, 1) + raw2 := types.AnyString(`{"name":"instance","type":"xlarge2"}`) + rawActual2 := make(chan *types.Any, 1) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes(map[string]instance.Plugin{ "type1": &testPlugin{ - DoValidate: func(req json.RawMessage) error { + DoValidate: func(req *types.Any) error { rawActual1 <- req @@ -86,7 +86,7 @@ func TestInstanceTypedPluginValidateError(t *testing.T) { }, }, "type2": &testPlugin{ - DoValidate: func(req json.RawMessage) error { + DoValidate: func(req *types.Any) error { rawActual2 <- req @@ -114,20 +114,20 @@ func TestInstanceTypedPluginProvision(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) - raw1 := json.RawMessage([]byte(`{"test":"foo"}`)) + raw1 := types.AnyString(`{"test":"foo"}`) specActual1 := make(chan instance.Spec, 1) - raw2 := json.RawMessage([]byte(`{"test":"foo2"}`)) + raw2 := types.AnyString(`{"test":"foo2"}`) specActual2 := make(chan instance.Spec, 1) - raw3 := json.RawMessage([]byte(`{"test":"foo3"}`)) + raw3 := types.AnyString(`{"test":"foo3"}`) specActual3 := make(chan instance.Spec, 1) spec1 := instance.Spec{ - Properties: &raw1, + Properties: raw1, } spec2 := instance.Spec{ - Properties: &raw2, + Properties: raw2, } spec3 := instance.Spec{ - Properties: &raw3, + Properties: raw3, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes( map[string]instance.Plugin{ diff --git a/pkg/rpc/instance/rpc_test.go b/pkg/rpc/instance/rpc_test.go index 2217c7709..002a65c98 100644 --- a/pkg/rpc/instance/rpc_test.go +++ b/pkg/rpc/instance/rpc_test.go @@ -1,7 +1,6 @@ package instance import ( - "encoding/json" "errors" "io/ioutil" "path" @@ -11,16 +10,20 @@ import ( "github.com/docker/infrakit/pkg/plugin" rpc_server "github.com/docker/infrakit/pkg/rpc/server" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/stretchr/testify/require" ) type testPlugin struct { // Validate performs local validation on a provision request. - DoValidate func(req json.RawMessage) error + DoValidate func(req *types.Any) error // Provision creates a new instance based on the spec. DoProvision func(spec instance.Spec) (*instance.ID, error) + // Label labels the instance + DoLabel func(instance instance.ID, labels map[string]string) error + // Destroy terminates an existing instance. DoDestroy func(instance instance.ID) error @@ -28,12 +31,15 @@ type testPlugin struct { DoDescribeInstances func(tags map[string]string) ([]instance.Description, error) } -func (t *testPlugin) Validate(req json.RawMessage) error { +func (t *testPlugin) Validate(req *types.Any) error { return t.DoValidate(req) } func (t *testPlugin) Provision(spec instance.Spec) (*instance.ID, error) { return t.DoProvision(spec) } +func (t *testPlugin) Label(instance instance.ID, labels map[string]string) error { + return t.DoLabel(instance, labels) +} func (t *testPlugin) Destroy(instance instance.ID) error { return t.DoDestroy(instance) } @@ -54,12 +60,12 @@ func TestInstancePluginValidate(t *testing.T) { socketPath := tempSocket() name := plugin.Name(filepath.Base(socketPath)) - raw := json.RawMessage([]byte(`{"name":"instance","type":"xlarge"}`)) + raw := types.AnyString(`{"name":"instance","type":"xlarge"}`) - rawActual := make(chan json.RawMessage, 1) + rawActual := make(chan *types.Any, 1) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoValidate: func(req json.RawMessage) error { + DoValidate: func(req *types.Any) error { rawActual <- req @@ -81,11 +87,11 @@ func TestInstancePluginValidateError(t *testing.T) { socketPath := tempSocket() name := plugin.Name(filepath.Base(socketPath)) - raw := json.RawMessage([]byte(`{"name":"instance","type":"xlarge"}`)) - rawActual := make(chan json.RawMessage, 1) + raw := types.AnyString(`{"name":"instance","type":"xlarge"}`) + rawActual := make(chan *types.Any, 1) server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ - DoValidate: func(req json.RawMessage) error { + DoValidate: func(req *types.Any) error { rawActual <- req @@ -106,10 +112,10 @@ func TestInstancePluginProvisionNil(t *testing.T) { socketPath := tempSocket() name := plugin.Name(filepath.Base(socketPath)) - raw := json.RawMessage([]byte(`{"test":"foo"}`)) + raw := types.AnyString(`{"test":"foo"}`) specActual := make(chan instance.Spec, 1) spec := instance.Spec{ - Properties: &raw, + Properties: raw, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ DoProvision: func(req instance.Spec) (*instance.ID, error) { @@ -133,10 +139,10 @@ func TestInstancePluginProvision(t *testing.T) { socketPath := tempSocket() name := plugin.Name(filepath.Base(socketPath)) - raw := json.RawMessage([]byte(`{"test":"foo"}`)) + raw := types.AnyString(`{"test":"foo"}`) specActual := make(chan instance.Spec, 1) spec := instance.Spec{ - Properties: &raw, + Properties: raw, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ DoProvision: func(req instance.Spec) (*instance.ID, error) { @@ -161,10 +167,10 @@ func TestInstancePluginProvisionError(t *testing.T) { socketPath := tempSocket() name := plugin.Name(filepath.Base(socketPath)) - raw := json.RawMessage([]byte(`{"test":"foo"}`)) + raw := types.AnyString(`{"test":"foo"}`) specActual := make(chan instance.Spec, 1) spec := instance.Spec{ - Properties: &raw, + Properties: raw, } server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ DoProvision: func(req instance.Spec) (*instance.ID, error) { diff --git a/pkg/rpc/instance/service.go b/pkg/rpc/instance/service.go index 4ef812c31..c93e11dc1 100644 --- a/pkg/rpc/instance/service.go +++ b/pkg/rpc/instance/service.go @@ -1,7 +1,6 @@ package instance import ( - "encoding/json" "fmt" "net/http" @@ -81,17 +80,12 @@ func (p *Instance) getPlugin(instanceType string) instance.Plugin { // Validate performs local validation on a provision request. func (p *Instance) Validate(_ *http.Request, req *ValidateRequest, resp *ValidateResponse) error { - var raw json.RawMessage - if req.Properties != nil { - raw = *req.Properties - } - c := p.getPlugin(req.Type) if c == nil { return fmt.Errorf("no-plugin:%s", req.Type) } resp.Type = req.Type - err := c.Validate(raw) + err := c.Validate(req.Properties) if err != nil { return err } @@ -114,6 +108,21 @@ func (p *Instance) Provision(_ *http.Request, req *ProvisionRequest, resp *Provi return nil } +// Label labels the instance +func (p *Instance) Label(_ *http.Request, req *LabelRequest, resp *LabelResponse) error { + resp.Type = req.Type + c := p.getPlugin(req.Type) + if c == nil { + return fmt.Errorf("no-plugin:%s", req.Type) + } + err := c.Label(req.Instance, req.Labels) + if err != nil { + return err + } + resp.OK = true + return nil +} + // Destroy terminates an existing instance. func (p *Instance) Destroy(_ *http.Request, req *DestroyRequest, resp *DestroyResponse) error { resp.Type = req.Type diff --git a/pkg/rpc/instance/types.go b/pkg/rpc/instance/types.go index 8c445f87e..b91f677b1 100644 --- a/pkg/rpc/instance/types.go +++ b/pkg/rpc/instance/types.go @@ -1,15 +1,14 @@ package instance import ( - "encoding/json" - "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) // ValidateRequest is the rpc wrapper for the Validate method args type ValidateRequest struct { Type string - Properties *json.RawMessage + Properties *types.Any } // ValidateResponse is the rpc wrapper for the Validate response values @@ -30,6 +29,19 @@ type ProvisionResponse struct { ID *instance.ID } +// LabelRequest is the rpc wrapper for Label request +type LabelRequest struct { + Type string + Instance instance.ID + Labels map[string]string +} + +// LabelResponse is the rpc wrapper for Label response +type LabelResponse struct { + Type string + OK bool +} + // DestroyRequest is the rpc wrapper for Destroy request type DestroyRequest struct { Type string diff --git a/pkg/rpc/server/info_test.go b/pkg/rpc/server/info_test.go index 8e715e689..b326f7727 100644 --- a/pkg/rpc/server/info_test.go +++ b/pkg/rpc/server/info_test.go @@ -17,6 +17,7 @@ import ( "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/group" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -31,12 +32,12 @@ type inputExampleWrapper struct { value interface{} } -func (i inputExampleWrapper) ExampleProperties() *json.RawMessage { +func (i inputExampleWrapper) ExampleProperties() *types.Any { buff, err := json.MarshalIndent(i.value, " ", " ") if err != nil { panic(err) } - raw := json.RawMessage(buff) + raw := types.Any(buff) return &raw } @@ -87,7 +88,7 @@ func TestMetaForInstance(t *testing.T) { require.Equal(t, vendorName, meta.Vendor.Name) require.Equal(t, vendorVersion, meta.Vendor.Version) require.Equal(t, 1, len(meta.Interfaces)) - require.Equal(t, 4, len(meta.Interfaces[0].Methods)) + require.Equal(t, 5, len(meta.Interfaces[0].Methods)) require.Equal(t, instance.InterfaceSpec, meta.Interfaces[0].InterfaceSpec) buff, err := json.MarshalIndent(meta, " ", " ") diff --git a/pkg/rpc/server/reflector.go b/pkg/rpc/server/reflector.go index 8f9f9880e..ca8bcebf0 100644 --- a/pkg/rpc/server/reflector.go +++ b/pkg/rpc/server/reflector.go @@ -1,7 +1,6 @@ package server import ( - "encoding/json" "fmt" "net/http" "reflect" @@ -13,6 +12,7 @@ import ( "github.com/docker/infrakit/pkg/plugin" "github.com/docker/infrakit/pkg/rpc" "github.com/docker/infrakit/pkg/spi" + "github.com/docker/infrakit/pkg/types" ) var ( @@ -32,7 +32,7 @@ func (r *reflector) VendorInfo() *spi.VendorInfo { return nil } -func (r *reflector) exampleProperties() *json.RawMessage { +func (r *reflector) exampleProperties() *types.Any { if example, is := r.target.(spi.InputExample); is { return example.ExampleProperties() } diff --git a/pkg/rpc/server/reflector_test.go b/pkg/rpc/server/reflector_test.go index de0d4bde1..535c626a6 100644 --- a/pkg/rpc/server/reflector_test.go +++ b/pkg/rpc/server/reflector_test.go @@ -9,6 +9,7 @@ import ( plugin_mock "github.com/docker/infrakit/pkg/mock/spi/instance" plugin_rpc "github.com/docker/infrakit/pkg/rpc/instance" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -28,7 +29,7 @@ func TestReflect(t *testing.T) { require.Equal(t, instance.InterfaceSpec, tver2) methods := r.pluginMethods() - require.Equal(t, 4, len(methods)) + require.Equal(t, 5, len(methods)) // get method names names := []string{} @@ -39,6 +40,7 @@ func TestReflect(t *testing.T) { expect := []string{ "Validate", "Provision", + "Label", "Destroy", "DescribeInstances", } @@ -62,9 +64,8 @@ func TestReflect(t *testing.T) { require.NoError(t, err) } -func toRaw(t *testing.T, v interface{}) *json.RawMessage { - buff, err := json.MarshalIndent(v, " ", " ") +func toRaw(t *testing.T, v interface{}) *types.Any { + any, err := types.AnyValue(v) require.NoError(t, err) - raw := json.RawMessage(buff) - return &raw + return any } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 0d7d5ee73..0a428866a 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1,7 +1,6 @@ package server import ( - "encoding/json" "errors" "fmt" "os" @@ -13,6 +12,7 @@ import ( "github.com/docker/infrakit/pkg/plugin" plugin_rpc "github.com/docker/infrakit/pkg/rpc/instance" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -31,7 +31,7 @@ func TestUnixSocketServer(t *testing.T) { Init: "init", } - properties := json.RawMessage([]byte(`{"foo":"bar"}`)) + properties := types.AnyString(`{"foo":"bar"}`) validateErr := errors.New("validate-error") gomock.InOrder( diff --git a/pkg/spi/flavor/spi.go b/pkg/spi/flavor/spi.go index 921b81c90..76aecf22a 100644 --- a/pkg/spi/flavor/spi.go +++ b/pkg/spi/flavor/spi.go @@ -1,10 +1,10 @@ package flavor import ( - "encoding/json" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) // InterfaceSpec is the current name and version of the Flavor API. @@ -31,16 +31,16 @@ const ( type Plugin interface { // Validate checks whether the helper can support a configuration. - Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error + Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error // Prepare allows the Flavor to modify the provisioning instructions for an instance. For example, a // helper could be used to place additional tags on the machine, or generate a specialized Init command based on // the flavor configuration. - Prepare(flavorProperties json.RawMessage, spec instance.Spec, allocation types.AllocationMethod) (instance.Spec, error) + Prepare(flavorProperties *types.Any, spec instance.Spec, allocation group_types.AllocationMethod) (instance.Spec, error) // Healthy determines the Health of this Flavor on an instance. - Healthy(flavorProperties json.RawMessage, inst instance.Description) (Health, error) + Healthy(flavorProperties *types.Any, inst instance.Description) (Health, error) // Drain allows the flavor to perform a best-effort cleanup operation before the instance is destroyed. - Drain(flavorProperties json.RawMessage, inst instance.Description) error + Drain(flavorProperties *types.Any, inst instance.Description) error } diff --git a/pkg/spi/instance/spi.go b/pkg/spi/instance/spi.go index 2fbfa2312..befb47292 100644 --- a/pkg/spi/instance/spi.go +++ b/pkg/spi/instance/spi.go @@ -26,5 +26,5 @@ type Plugin interface { Destroy(instance ID) error // DescribeInstances returns descriptions of all instances matching all of the provided tags. - DescribeInstances(tags map[string]string) ([]Description, error) + DescribeInstances(labels map[string]string) ([]Description, error) } diff --git a/pkg/spi/instance/types.go b/pkg/spi/instance/types.go index 338393f88..50feb6b52 100644 --- a/pkg/spi/instance/types.go +++ b/pkg/spi/instance/types.go @@ -1,7 +1,7 @@ package instance import ( - "encoding/json" + "github.com/docker/infrakit/pkg/types" ) // ID is the identifier for an instance. @@ -30,7 +30,7 @@ type Attachment struct { // Spec is a specification of an instance to be provisioned type Spec struct { // Properties is the opaque instance plugin configuration. - Properties *json.RawMessage + Properties *types.Any // Tags are metadata that describes an instance. Tags map[string]string diff --git a/pkg/spi/plugin.go b/pkg/spi/plugin.go index b920d46e8..fb7f70350 100644 --- a/pkg/spi/plugin.go +++ b/pkg/spi/plugin.go @@ -1,7 +1,7 @@ package spi import ( - "encoding/json" + "github.com/docker/infrakit/pkg/types" ) // InterfaceSpec is metadata about an API. @@ -35,5 +35,5 @@ type InputExample interface { // ExampleProperties returns an example JSON raw message that the vendor plugin understands. // This is an example of what the user will configure and what will be used as the opaque // blob in all the plugin methods where raw JSON messages are referenced. - ExampleProperties() *json.RawMessage + ExampleProperties() *types.Any } diff --git a/pkg/template/fetch_test.go b/pkg/template/fetch_test.go index aeb0ce94e..c5e537ddc 100644 --- a/pkg/template/fetch_test.go +++ b/pkg/template/fetch_test.go @@ -1,7 +1,6 @@ package template import ( - "encoding/json" "io/ioutil" "path" "path/filepath" @@ -10,16 +9,20 @@ import ( rpc "github.com/docker/infrakit/pkg/rpc/instance" rpc_server "github.com/docker/infrakit/pkg/rpc/server" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/stretchr/testify/require" ) type testPlugin struct { // Validate performs local validation on a provision request. - DoValidate func(req json.RawMessage) error + DoValidate func(req *types.Any) error // Provision creates a new instance based on the spec. DoProvision func(spec instance.Spec) (*instance.ID, error) + // Label labels the resource + DoLabel func(instance instance.ID, labels map[string]string) error + // Destroy terminates an existing instance. DoDestroy func(instance instance.ID) error @@ -27,12 +30,15 @@ type testPlugin struct { DoDescribeInstances func(tags map[string]string) ([]instance.Description, error) } -func (t *testPlugin) Validate(req json.RawMessage) error { +func (t *testPlugin) Validate(req *types.Any) error { return t.DoValidate(req) } func (t *testPlugin) Provision(spec instance.Spec) (*instance.ID, error) { return t.DoProvision(spec) } +func (t *testPlugin) Label(instance instance.ID, labels map[string]string) error { + return t.DoLabel(instance, labels) +} func (t *testPlugin) Destroy(instance instance.ID) error { return t.DoDestroy(instance) } diff --git a/pkg/types/any.go b/pkg/types/any.go index c579774dd..a21f2938a 100644 --- a/pkg/types/any.go +++ b/pkg/types/any.go @@ -19,6 +19,14 @@ func AnyBytes(data []byte) *Any { return any } +// AnyCopy makes a copy of the data in the given ptr. +func AnyCopy(any *Any) *Any { + if any == nil { + return &Any{} + } + return AnyBytes(any.Bytes()) +} + // AnyValue returns an Any from a value by marshaling / encoding the input func AnyValue(v interface{}) (*Any, error) { if v == nil { From aa0c51cb314eadf699eb910db7c8ec84c5f0ea42 Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 26 Jan 2017 16:36:40 -0800 Subject: [PATCH 03/10] updating examples/ packages Signed-off-by: David Chung --- examples/flavor/combo/flavor.go | 34 +++++++++---------- examples/flavor/combo/flavor_test.go | 33 ++++++++---------- examples/flavor/swarm/flavor.go | 12 +++---- examples/flavor/swarm/flavor_test.go | 44 ++++++++++++------------ examples/flavor/swarm/manager.go | 14 ++++---- examples/flavor/swarm/worker.go | 14 ++++---- examples/flavor/vanilla/flavor.go | 19 +++++------ examples/flavor/zookeeper/flavor.go | 24 +++++++------- examples/instance/file/plugin.go | 48 +++++++++++++++++++++------ examples/instance/terraform/plugin.go | 18 ++++++---- examples/instance/vagrant/instance.go | 38 +++++++++++++++++---- 11 files changed, 176 insertions(+), 122 deletions(-) diff --git a/examples/flavor/combo/flavor.go b/examples/flavor/combo/flavor.go index 65ba8417a..cc66c95eb 100644 --- a/examples/flavor/combo/flavor.go +++ b/examples/flavor/combo/flavor.go @@ -1,18 +1,19 @@ package main import ( - "encoding/json" "errors" + "strings" + "github.com/docker/infrakit/pkg/plugin/group" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" - "strings" + "github.com/docker/infrakit/pkg/types" ) // Spec is the model of the plugin Properties. type Spec struct { - Flavors []types.FlavorPlugin + Flavors []group_types.FlavorPlugin } // NewPlugin creates a Flavor Combo plugin that chains multiple flavors in a sequence. Each flavor @@ -24,18 +25,18 @@ type flavorCombo struct { flavorPlugins group.FlavorPluginLookup } -func (f flavorCombo) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (f flavorCombo) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { s := Spec{} - return json.Unmarshal(flavorProperties, &s) + return flavorProperties.Decode(&s) } -func (f flavorCombo) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (f flavorCombo) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { // The overall health of the flavor combination is taken as the 'lowest common demoninator' of the configured // flavors. Only flavor.Healthy is reported if all flavors report flavor.Healthy. flavor.Unhealthy or // flavor.UnknownHealth is returned as soon as any Flavor reports that value. s := Spec{} - if err := json.Unmarshal(flavorProperties, &s); err != nil { + if err := flavorProperties.Decode(s); err != nil { return flavor.Unknown, err } @@ -45,7 +46,7 @@ func (f flavorCombo) Healthy(flavorProperties json.RawMessage, inst instance.Des return flavor.Unknown, err } - health, err := plugin.Healthy(types.RawMessage(pluginSpec.Properties), inst) + health, err := plugin.Healthy(pluginSpec.Properties, inst) if err != nil || health != flavor.Healthy { return health, err } @@ -54,12 +55,12 @@ func (f flavorCombo) Healthy(flavorProperties json.RawMessage, inst instance.Des return flavor.Healthy, nil } -func (f flavorCombo) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (f flavorCombo) Drain(flavorProperties *types.Any, inst instance.Description) error { // Draining is attempted on all flavors regardless of errors encountered. All errors encountered are combined // and returned. s := Spec{} - if err := json.Unmarshal(flavorProperties, &s); err != nil { + if err := flavorProperties.Decode(&s); err != nil { return err } @@ -71,7 +72,7 @@ func (f flavorCombo) Drain(flavorProperties json.RawMessage, inst instance.Descr errs = append(errs, err.Error()) } - if err := plugin.Drain(types.RawMessage(pluginSpec.Properties), inst); err != nil { + if err := plugin.Drain(pluginSpec.Properties, inst); err != nil { errs = append(errs, err.Error()) } } @@ -133,13 +134,12 @@ func mergeSpecs(initial instance.Spec, specs []instance.Spec) (instance.Spec, er return result, nil } -func (f flavorCombo) Prepare( - flavor json.RawMessage, +func (f flavorCombo) Prepare(flavor *types.Any, inst instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { combo := Spec{} - err := json.Unmarshal(flavor, &combo) + err := flavor.Decode(&combo) if err != nil { return inst, err } @@ -154,7 +154,7 @@ func (f flavorCombo) Prepare( return inst, err } - flavorOutput, err := plugin.Prepare(types.RawMessage(pluginSpec.Properties), clone, allocation) + flavorOutput, err := plugin.Prepare(pluginSpec.Properties, clone, allocation) if err != nil { return inst, err } diff --git a/examples/flavor/combo/flavor_test.go b/examples/flavor/combo/flavor_test.go index e531b9d47..4861e484d 100644 --- a/examples/flavor/combo/flavor_test.go +++ b/examples/flavor/combo/flavor_test.go @@ -1,32 +1,27 @@ package main import ( - "encoding/json" "errors" "testing" mock_flavor "github.com/docker/infrakit/pkg/mock/spi/flavor" "github.com/docker/infrakit/pkg/plugin" "github.com/docker/infrakit/pkg/plugin/group" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) -func jsonPtr(v string) *json.RawMessage { - j := json.RawMessage(v) - return &j -} - func logicalID(v string) *instance.LogicalID { id := instance.LogicalID(v) return &id } var inst = instance.Spec{ - Properties: jsonPtr("{}"), + Properties: types.AnyString("{}"), Tags: map[string]string{}, Init: "", LogicalID: logicalID("id"), @@ -54,7 +49,7 @@ func TestMergeBehavior(t *testing.T) { combo := NewPlugin(pluginLookup(plugins)) - flavorProperties := json.RawMessage(`{ + flavorProperties := types.AnyString(`{ "Flavors": [ { "Plugin": "a", @@ -67,9 +62,9 @@ func TestMergeBehavior(t *testing.T) { ] }`) - allocation := types.AllocationMethod{Size: 1} + allocation := group_types.AllocationMethod{Size: 1} - a.EXPECT().Prepare(json.RawMessage(`{"a": "1"}`), inst, allocation).Return(instance.Spec{ + a.EXPECT().Prepare(types.AnyString(`{"a": "1"}`), inst, allocation).Return(instance.Spec{ Properties: inst.Properties, Tags: map[string]string{"a": "1", "c": "4"}, Init: "init data a", @@ -77,7 +72,7 @@ func TestMergeBehavior(t *testing.T) { Attachments: []instance.Attachment{{ID: "a", Type: "nic"}}, }, nil) - b.EXPECT().Prepare(json.RawMessage(`{"b": "2"}`), inst, allocation).Return(instance.Spec{ + b.EXPECT().Prepare(types.AnyString(`{"b": "2"}`), inst, allocation).Return(instance.Spec{ Properties: inst.Properties, Tags: map[string]string{"b": "2", "c": "5"}, Init: "init data b", @@ -85,7 +80,7 @@ func TestMergeBehavior(t *testing.T) { Attachments: []instance.Attachment{{ID: "b", Type: "gpu"}}, }, nil) - result, err := combo.Prepare(flavorProperties, inst, types.AllocationMethod{Size: 1}) + result, err := combo.Prepare(flavorProperties, inst, group_types.AllocationMethod{Size: 1}) require.NoError(t, err) expected := instance.Spec{ @@ -102,7 +97,7 @@ func TestMergeNoLogicalID(t *testing.T) { // Tests regression of a bug where a zero value was returned for the LogicalID despite nil being passed. inst = instance.Spec{ - Properties: jsonPtr("{}"), + Properties: types.AnyString("{}"), Tags: map[string]string{}, Init: "", Attachments: []instance.Attachment{{ID: "att1", Type: "nic"}}, @@ -118,7 +113,7 @@ func TestMergeNoLogicalID(t *testing.T) { combo := NewPlugin(pluginLookup(plugins)) - flavorProperties := json.RawMessage(`{ + flavorProperties := types.AnyString(`{ "Flavors": [ { "Plugin": "a", @@ -131,9 +126,9 @@ func TestMergeNoLogicalID(t *testing.T) { ] }`) - allocation := types.AllocationMethod{Size: 1} + allocation := group_types.AllocationMethod{Size: 1} - a.EXPECT().Prepare(json.RawMessage(`{"a": "1"}`), inst, allocation).Return(instance.Spec{ + a.EXPECT().Prepare(types.AnyString(`{"a": "1"}`), inst, allocation).Return(instance.Spec{ Properties: inst.Properties, Tags: map[string]string{"a": "1", "c": "4"}, Init: "init data a", @@ -141,7 +136,7 @@ func TestMergeNoLogicalID(t *testing.T) { Attachments: []instance.Attachment{{ID: "a", Type: "nic"}}, }, nil) - b.EXPECT().Prepare(json.RawMessage(`{"b": "2"}`), inst, allocation).Return(instance.Spec{ + b.EXPECT().Prepare(types.AnyString(`{"b": "2"}`), inst, allocation).Return(instance.Spec{ Properties: inst.Properties, Tags: map[string]string{"b": "2", "c": "5"}, Init: "init data b", @@ -149,7 +144,7 @@ func TestMergeNoLogicalID(t *testing.T) { Attachments: []instance.Attachment{{ID: "b", Type: "gpu"}}, }, nil) - result, err := combo.Prepare(flavorProperties, inst, types.AllocationMethod{Size: 1}) + result, err := combo.Prepare(flavorProperties, inst, group_types.AllocationMethod{Size: 1}) require.NoError(t, err) expected := instance.Spec{ diff --git a/examples/flavor/swarm/flavor.go b/examples/flavor/swarm/flavor.go index 416df324d..0dc760b3b 100644 --- a/examples/flavor/swarm/flavor.go +++ b/examples/flavor/swarm/flavor.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "encoding/json" "errors" "fmt" @@ -10,10 +9,11 @@ import ( docker_types "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/template" + "github.com/docker/infrakit/pkg/types" "golang.org/x/net/context" ) @@ -31,9 +31,9 @@ type schema struct { DockerRestartCommand string } -func parseProperties(flavorProperties json.RawMessage) (schema, error) { +func parseProperties(flavorProperties *types.Any) (schema, error) { s := schema{} - err := json.Unmarshal(flavorProperties, &s) + err := flavorProperties.Decode(&s) return s, err } @@ -109,7 +109,7 @@ func generateInitScript(templ *template.Template, return buffer.String(), nil } -func (s swarmFlavor) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (s swarmFlavor) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { properties, err := parseProperties(flavorProperties) if err != nil { return err @@ -126,7 +126,7 @@ func (s swarmFlavor) Validate(flavorProperties json.RawMessage, allocation types // Healthy determines whether an instance is healthy. This is determined by whether it has successfully joined the // Swarm. func healthy(client client.APIClient, - flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { + flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { associationID, exists := inst.Tags[associationTag] if !exists { diff --git a/examples/flavor/swarm/flavor_test.go b/examples/flavor/swarm/flavor_test.go index 2fc3ae620..3815b4937 100644 --- a/examples/flavor/swarm/flavor_test.go +++ b/examples/flavor/swarm/flavor_test.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "testing" @@ -9,10 +8,11 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" mock_client "github.com/docker/infrakit/pkg/mock/docker/docker/client" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/template" + "github.com/docker/infrakit/pkg/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -33,41 +33,41 @@ func TestValidate(t *testing.T) { workerFlavor := NewWorkerFlavor(mock_client.NewMockAPIClient(ctrl), templ()) require.NoError(t, workerFlavor.Validate( - json.RawMessage(`{"DockerRestartCommand": "systemctl restart docker"}`), - types.AllocationMethod{Size: 5})) + types.AnyString(`{"DockerRestartCommand": "systemctl restart docker"}`), + group_types.AllocationMethod{Size: 5})) require.NoError(t, managerFlavor.Validate( - json.RawMessage(`{"DockerRestartCommand": "systemctl restart docker"}`), - types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}})) + types.AnyString(`{"DockerRestartCommand": "systemctl restart docker"}`), + group_types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}})) // Logical ID with multiple attachments is allowed. require.NoError(t, managerFlavor.Validate( - json.RawMessage(`{ + types.AnyString(`{ "DockerRestartCommand": "systemctl restart docker", "Attachments": {"127.0.0.1": [{"ID": "a", "Type": "ebs"}, {"ID": "b", "Type": "ebs"}]}}`), - types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}})) + group_types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}})) // Logical ID used more than once. err := managerFlavor.Validate( - json.RawMessage(`{"DockerRestartCommand": "systemctl restart docker"}`), - types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1", "127.0.0.1", "127.0.0.2"}}) + types.AnyString(`{"DockerRestartCommand": "systemctl restart docker"}`), + group_types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1", "127.0.0.1", "127.0.0.2"}}) require.Error(t, err) require.Equal(t, "LogicalID 127.0.0.1 specified more than once", err.Error()) // Attachment cannot be associated with multiple Logical IDs. err = managerFlavor.Validate( - json.RawMessage(`{ + types.AnyString(`{ "DockerRestartCommand": "systemctl restart docker", "Attachments": {"127.0.0.1": [{"ID": "a", "Type": "ebs"}], "127.0.0.2": [{"ID": "a", "Type": "ebs"}]}}`), - types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1", "127.0.0.2", "127.0.0.3"}}) + group_types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1", "127.0.0.2", "127.0.0.3"}}) require.Error(t, err) require.Equal(t, "Attachment a specified more than once", err.Error()) // Unsupported Attachment Type. err = managerFlavor.Validate( - json.RawMessage(`{ + types.AnyString(`{ "DockerRestartCommand": "systemctl restart docker", "Attachments": {"127.0.0.1": [{"ID": "a", "Type": "keyboard"}]}}`), - types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}}) + group_types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}}) require.Error(t, err) require.Equal(t, "Invalid attachment Type 'keyboard', only ebs is supported", err.Error()) } @@ -95,9 +95,9 @@ func TestWorker(t *testing.T) { client.EXPECT().NodeInspectWithRaw(gomock.Any(), nodeID).Return(nodeInfo, nil, nil) details, err := flavorImpl.Prepare( - json.RawMessage(`{}`), + types.AnyString(`{}`), instance.Spec{Tags: map[string]string{"a": "b"}}, - types.AllocationMethod{Size: 5}) + group_types.AllocationMethod{Size: 5}) require.NoError(t, err) require.Equal(t, "b", details.Tags["a"]) associationID := details.Tags[associationTag] @@ -113,7 +113,7 @@ func TestWorker(t *testing.T) { require.Empty(t, details.Attachments) // An instance with no association information is considered unhealthy. - health, err := flavorImpl.Healthy(json.RawMessage("{}"), instance.Description{}) + health, err := flavorImpl.Healthy(types.AnyString("{}"), instance.Description{}) require.NoError(t, err) require.Equal(t, flavor.Unhealthy, health) @@ -124,7 +124,7 @@ func TestWorker(t *testing.T) { {}, }, nil) health, err = flavorImpl.Healthy( - json.RawMessage("{}"), + types.AnyString("{}"), instance.Description{Tags: map[string]string{associationTag: associationID}}) require.NoError(t, err) require.Equal(t, flavor.Healthy, health) @@ -158,9 +158,9 @@ func TestManager(t *testing.T) { id := instance.LogicalID("127.0.0.1") details, err := flavorImpl.Prepare( - json.RawMessage(`{"Attachments": {"127.0.0.1": [{"ID": "a", "Type": "gpu"}]}}`), + types.AnyString(`{"Attachments": {"127.0.0.1": [{"ID": "a", "Type": "gpu"}]}}`), instance.Spec{Tags: map[string]string{"a": "b"}, LogicalID: &id}, - types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}}) + group_types.AllocationMethod{LogicalIDs: []instance.LogicalID{"127.0.0.1"}}) require.NoError(t, err) require.Equal(t, "b", details.Tags["a"]) associationID := details.Tags[associationTag] @@ -176,7 +176,7 @@ func TestManager(t *testing.T) { require.Equal(t, []instance.Attachment{{ID: "a", Type: "gpu"}}, details.Attachments) // An instance with no association information is considered unhealthy. - health, err := flavorImpl.Healthy(json.RawMessage("{}"), instance.Description{}) + health, err := flavorImpl.Healthy(types.AnyString("{}"), instance.Description{}) require.NoError(t, err) require.Equal(t, flavor.Unhealthy, health) @@ -187,7 +187,7 @@ func TestManager(t *testing.T) { {}, }, nil) health, err = flavorImpl.Healthy( - json.RawMessage("{}"), + types.AnyString("{}"), instance.Description{Tags: map[string]string{associationTag: associationID}}) require.NoError(t, err) require.Equal(t, flavor.Healthy, health) diff --git a/examples/flavor/swarm/manager.go b/examples/flavor/swarm/manager.go index 3a588b4ac..decf90fef 100644 --- a/examples/flavor/swarm/manager.go +++ b/examples/flavor/swarm/manager.go @@ -1,17 +1,17 @@ package main import ( - "encoding/json" "errors" "fmt" log "github.com/Sirupsen/logrus" "github.com/docker/docker/client" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/plugin/group/util" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/template" + "github.com/docker/infrakit/pkg/types" "golang.org/x/net/context" ) @@ -25,7 +25,7 @@ type managerFlavor struct { initScript *template.Template } -func (s *managerFlavor) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (s *managerFlavor) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { properties, err := parseProperties(flavorProperties) if err != nil { return err @@ -54,12 +54,12 @@ func (s *managerFlavor) Validate(flavorProperties json.RawMessage, allocation ty // Healthy determines whether an instance is healthy. This is determined by whether it has successfully joined the // Swarm. -func (s *managerFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (s *managerFlavor) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { return healthy(s.client, flavorProperties, inst) } -func (s *managerFlavor) Prepare(flavorProperties json.RawMessage, - spec instance.Spec, allocation types.AllocationMethod) (instance.Spec, error) { +func (s *managerFlavor) Prepare(flavorProperties *types.Any, + spec instance.Spec, allocation group_types.AllocationMethod) (instance.Spec, error) { properties, err := parseProperties(flavorProperties) if err != nil { @@ -120,6 +120,6 @@ func (s *managerFlavor) Prepare(flavorProperties json.RawMessage, // Drain only explicitly remove worker nodes, not manager nodes. Manager nodes are assumed to have an // attached volume for state, and fixed IP addresses. This allows them to rejoin as the same node. -func (s *managerFlavor) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (s *managerFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error { return nil } diff --git a/examples/flavor/swarm/worker.go b/examples/flavor/swarm/worker.go index 578af2b05..4c20db8c2 100644 --- a/examples/flavor/swarm/worker.go +++ b/examples/flavor/swarm/worker.go @@ -1,18 +1,18 @@ package main import ( - "encoding/json" "errors" "fmt" docker_types "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/plugin/group/util" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/template" + "github.com/docker/infrakit/pkg/types" "golang.org/x/net/context" ) @@ -26,7 +26,7 @@ type workerFlavor struct { initScript *template.Template } -func (s *workerFlavor) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (s *workerFlavor) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { properties, err := parseProperties(flavorProperties) if err != nil { return err @@ -45,12 +45,12 @@ func (s *workerFlavor) Validate(flavorProperties json.RawMessage, allocation typ // Healthy determines whether an instance is healthy. This is determined by whether it has successfully joined the // Swarm. -func (s *workerFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (s *workerFlavor) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { return healthy(s.client, flavorProperties, inst) } -func (s *workerFlavor) Prepare(flavorProperties json.RawMessage, spec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { +func (s *workerFlavor) Prepare(flavorProperties *types.Any, spec instance.Spec, + allocation group_types.AllocationMethod) (instance.Spec, error) { properties, err := parseProperties(flavorProperties) if err != nil { @@ -105,7 +105,7 @@ func (s *workerFlavor) Prepare(flavorProperties json.RawMessage, spec instance.S return spec, nil } -func (s *workerFlavor) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (s *workerFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error { associationID, exists := inst.Tags[associationTag] if !exists { diff --git a/examples/flavor/vanilla/flavor.go b/examples/flavor/vanilla/flavor.go index 917987602..ed57017e7 100644 --- a/examples/flavor/vanilla/flavor.go +++ b/examples/flavor/vanilla/flavor.go @@ -1,12 +1,12 @@ package main import ( - "encoding/json" "strings" - "github.com/docker/infrakit/pkg/plugin/group/types" + group_types "github.com/docker/infrakit/pkg/plugin/group/types" "github.com/docker/infrakit/pkg/spi/flavor" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) // Spec is the model of the Properties section of the top level group spec. @@ -28,27 +28,26 @@ func NewPlugin() flavor.Plugin { type vanillaFlavor int -func (f vanillaFlavor) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { - return json.Unmarshal(flavorProperties, &Spec{}) +func (f vanillaFlavor) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { + return flavorProperties.Decode(&Spec{}) } -func (f vanillaFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (f vanillaFlavor) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { // TODO: We could add support for shell code in the Spec for a command to run for checking health. return flavor.Healthy, nil } -func (f vanillaFlavor) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (f vanillaFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error { // TODO: We could add support for shell code in the Spec for a drain command to run. return nil } -func (f vanillaFlavor) Prepare( - flavor json.RawMessage, +func (f vanillaFlavor) Prepare(flavor *types.Any, instance instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { s := Spec{} - err := json.Unmarshal(flavor, &s) + err := flavor.Decode(&s) if err != nil { return instance, err } diff --git a/examples/flavor/zookeeper/flavor.go b/examples/flavor/zookeeper/flavor.go index 669993794..c45058e4b 100644 --- a/examples/flavor/zookeeper/flavor.go +++ b/examples/flavor/zookeeper/flavor.go @@ -2,14 +2,15 @@ package main import ( "bytes" - "encoding/json" "errors" "fmt" - "github.com/docker/infrakit/pkg/plugin/group/types" - "github.com/docker/infrakit/pkg/spi/flavor" - "github.com/docker/infrakit/pkg/spi/instance" "strings" "text/template" + + group_types "github.com/docker/infrakit/pkg/plugin/group/types" + "github.com/docker/infrakit/pkg/spi/flavor" + "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" ) const ( @@ -29,13 +30,13 @@ type spec struct { UseDocker bool } -func parseSpec(flavorProperties json.RawMessage) (spec, error) { +func parseSpec(flavorProperties *types.Any) (spec, error) { s := spec{UseDocker: false} - err := json.Unmarshal(flavorProperties, &s) + err := flavorProperties.Decode(&s) return s, err } -func (z zkFlavor) Validate(flavorProperties json.RawMessage, allocation types.AllocationMethod) error { +func (z zkFlavor) Validate(flavorProperties *types.Any, allocation group_types.AllocationMethod) error { properties, err := parseSpec(flavorProperties) if err != nil { return err @@ -146,20 +147,19 @@ func generateInitScript(useDocker bool, servers []instance.LogicalID, id instanc } // Healthy determines whether an instance is healthy. -func (z zkFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Description) (flavor.Health, error) { +func (z zkFlavor) Healthy(flavorProperties *types.Any, inst instance.Description) (flavor.Health, error) { // TODO(wfarner): Implement. return flavor.Healthy, nil } -func (z zkFlavor) Drain(flavorProperties json.RawMessage, inst instance.Description) error { +func (z zkFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error { // There doesn't seem to be any need to drain ZK members, especially if they are expected to return. return nil } -func (z zkFlavor) Prepare( - flavorProperties json.RawMessage, +func (z zkFlavor) Prepare(flavorProperties *types.Any, spec instance.Spec, - allocation types.AllocationMethod) (instance.Spec, error) { + allocation group_types.AllocationMethod) (instance.Spec, error) { properties, err := parseSpec(flavorProperties) if err != nil { diff --git a/examples/instance/file/plugin.go b/examples/instance/file/plugin.go index 2a4e9c0a3..0d86f61ec 100644 --- a/examples/instance/file/plugin.go +++ b/examples/instance/file/plugin.go @@ -9,6 +9,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/infrakit/pkg/spi" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/spf13/afero" "math/rand" ) @@ -59,25 +60,24 @@ func (p *plugin) VendorInfo() *spi.VendorInfo { } // ExampleProperties returns the properties / config of this plugin -func (p *plugin) ExampleProperties() *json.RawMessage { - buff, err := json.MarshalIndent(Spec{ +func (p *plugin) ExampleProperties() *types.Any { + any, err := types.AnyValue(Spec{ "exampleString": "a_string", "exampleBool": true, "exampleInt": 1, - }, " ", " ") + }) if err != nil { - panic(err) + return nil } - raw := json.RawMessage(buff) - return &raw + return any } // Validate performs local validation on a provision request. -func (p *plugin) Validate(req json.RawMessage) error { - log.Debugln("validate", string(req)) +func (p *plugin) Validate(req *types.Any) error { + log.Debugln("validate", req.String()) spec := Spec{} - if err := json.Unmarshal(req, &spec); err != nil { + if err := req.Decode(&spec); err != nil { return err } @@ -97,7 +97,7 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { LogicalID: spec.LogicalID, }, Spec: spec, - }, " ", " ") + }, "", "") log.Debugln("provision", id, "data=", string(buff), "err=", err) if err != nil { return nil, err @@ -105,6 +105,34 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { return &id, afero.WriteFile(p.fs, filepath.Join(p.Dir, string(id)), buff, 0644) } +// Label labels the instance +func (p *plugin) Label(instance instance.ID, labels map[string]string) error { + fp := filepath.Join(p.Dir, string(instance)) + buff, err := afero.ReadFile(p.fs, fp) + if err != nil { + return err + } + instanceData := fileInstance{} + err = json.Unmarshal(buff, &instanceData) + if err != nil { + return err + } + + if instanceData.Description.Tags == nil { + instanceData.Description.Tags = map[string]string{} + } + for k, v := range labels { + instanceData.Description.Tags[k] = v + } + + buff, err = json.MarshalIndent(instanceData, "", "") + log.Debugln("label:", instance, "data=", string(buff), "err=", err) + if err != nil { + return err + } + return afero.WriteFile(p.fs, fp, buff, 0644) +} + // Destroy terminates an existing instance. func (p *plugin) Destroy(instance instance.ID) error { fp := filepath.Join(p.Dir, string(instance)) diff --git a/examples/instance/terraform/plugin.go b/examples/instance/terraform/plugin.go index 39eaea461..436874834 100644 --- a/examples/instance/terraform/plugin.go +++ b/examples/instance/terraform/plugin.go @@ -17,6 +17,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" "github.com/nightlyone/lockfile" "github.com/spf13/afero" ) @@ -139,21 +140,21 @@ type SpecPropertiesFormat struct { } // Validate performs local validation on a provision request. -func (p *plugin) Validate(req json.RawMessage) error { - log.Debugln("validate", string(req)) +func (p *plugin) Validate(req *types.Any) error { + log.Debugln("validate", req.String()) parsed := SpecPropertiesFormat{} - err := json.Unmarshal([]byte(req), &parsed) + err := req.Decode(&parsed) if err != nil { return err } if parsed.Type == "" { - return fmt.Errorf("no-resource-type:%s", string(req)) + return fmt.Errorf("no-resource-type:%s", req.String()) } if len(parsed.Value) == 0 { - return fmt.Errorf("no-value:%s", string(req)) + return fmt.Errorf("no-value:%s", req.String()) } return nil } @@ -335,7 +336,7 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { } properties := SpecPropertiesFormat{} - err := json.Unmarshal(*spec.Properties, &properties) + err := spec.Properties.Decode(&properties) if err != nil { return nil, err } @@ -425,6 +426,11 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { return &id, p.terraformApply() } +// Label labels the instance +func (p *plugin) Label(instance instance.ID, labels map[string]string) error { + return fmt.Errorf("not implemented!!") +} + // Destroy terminates an existing instance. func (p *plugin) Destroy(instance instance.ID) error { fp := filepath.Join(p.Dir, string(instance)+".tf.json") diff --git a/examples/instance/vagrant/instance.go b/examples/instance/vagrant/instance.go index a277e4b77..b5277a901 100644 --- a/examples/instance/vagrant/instance.go +++ b/examples/instance/vagrant/instance.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "encoding/json" "errors" "fmt" "io/ioutil" @@ -12,6 +11,7 @@ import ( "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/template" + "github.com/docker/infrakit/pkg/types" ) // NewVagrantPlugin creates an instance plugin for vagrant. @@ -25,7 +25,7 @@ type vagrantPlugin struct { } // Validate performs local validation on a provision request. -func (v vagrantPlugin) Validate(req json.RawMessage) error { +func (v vagrantPlugin) Validate(req *types.Any) error { return nil } @@ -46,7 +46,7 @@ func (v vagrantPlugin) Provision(spec instance.Spec) (*instance.ID, error) { var properties map[string]interface{} if spec.Properties != nil { - if err := json.Unmarshal(*spec.Properties, &properties); err != nil { + if err := spec.Properties.Decode(&properties); err != nil { return nil, fmt.Errorf("Invalid instance properties: %s", err) } } @@ -97,12 +97,12 @@ func (v vagrantPlugin) Provision(spec instance.Spec) (*instance.ID, error) { return nil, err } - tagData, err := json.Marshal(spec.Tags) + tagData, err := types.AnyValue(spec.Tags) if err != nil { return nil, err } - if err := ioutil.WriteFile(path.Join(machineDir, "tags"), tagData, 0666); err != nil { + if err := ioutil.WriteFile(path.Join(machineDir, "tags"), tagData.Bytes(), 0666); err != nil { return nil, err } @@ -115,6 +115,32 @@ func (v vagrantPlugin) Provision(spec instance.Spec) (*instance.ID, error) { return &id, nil } +// Label labels the instance +func (v vagrantPlugin) Label(instance instance.ID, labels map[string]string) error { + machineDir := path.Join(v.VagrantfilesDir, string(instance)) + tagFile := path.Join(machineDir, "tags") + buff, err := ioutil.ReadFile(tagFile) + if err != nil { + return err + } + + tags := map[string]string{} + err = types.AnyBytes(buff).Decode(&tags) + if err != nil { + return err + } + + for k, v := range labels { + tags[k] = v + } + + encoded, err := types.AnyValue(tags) + if err != nil { + return err + } + return ioutil.WriteFile(tagFile, encoded.Bytes(), 0666) +} + // Destroy terminates an existing instance. func (v vagrantPlugin) Destroy(id instance.ID) error { fmt.Println("Destroying ", id) @@ -165,7 +191,7 @@ func (v vagrantPlugin) DescribeInstances(tags map[string]string) ([]instance.Des } machineTags := map[string]string{} - if err := json.Unmarshal(tagData, &machineTags); err != nil { + if err := types.AnyBytes(tagData).Decode(&machineTags); err != nil { return nil, err } From ffe4492c4168be6b847f2cc30dbb6cdc1e9c4dc4 Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 26 Jan 2017 22:31:23 -0800 Subject: [PATCH 04/10] fix lint Signed-off-by: David Chung --- examples/instance/terraform/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/instance/terraform/plugin.go b/examples/instance/terraform/plugin.go index 436874834..635bce5c1 100644 --- a/examples/instance/terraform/plugin.go +++ b/examples/instance/terraform/plugin.go @@ -428,7 +428,7 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { // Label labels the instance func (p *plugin) Label(instance instance.ID, labels map[string]string) error { - return fmt.Errorf("not implemented!!") + return fmt.Errorf("not implemented") } // Destroy terminates an existing instance. From 01abedc0d1a41f03b78f3d41681c74357105932f Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 26 Jan 2017 22:43:29 -0800 Subject: [PATCH 05/10] update docs Signed-off-by: David Chung --- docs/plugins/flavor.md | 2 +- docs/plugins/instance.md | 31 ++++++++++++++++++++++++++++++- docs/plugins/types.md | 2 ++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/plugins/flavor.md b/docs/plugins/flavor.md index 0fb00a091..af8603054 100644 --- a/docs/plugins/flavor.md +++ b/docs/plugins/flavor.md @@ -1,6 +1,6 @@ # Flavor plugin API - + ## API diff --git a/docs/plugins/instance.md b/docs/plugins/instance.md index daa1e2fa7..b398f6518 100644 --- a/docs/plugins/instance.md +++ b/docs/plugins/instance.md @@ -1,6 +1,6 @@ # Instance plugin API - + ## API @@ -80,6 +80,35 @@ Parameters: Fields: - `OK`: Whether the operation succeeded. +### Method `Instance.Label` +Labels an instance. The plugin should add or update the labels given. + +#### Request +```json +{ + "Instance": "instance_id", + "Labels" : { + "label1" : "value1", + "label2" : "value2", + "label3" : "value3" + } +} +``` + +Parameters: +- `Instance`: An [instance ID](types.md#instance-id). +- `Labels`: A [map](types.md#instance-tags) of labels or instance tags. + +#### Response +```json +{ + "OK": true +} +``` + +Fields: +- `OK`: Whether the operation succeeded. + ### Method `Instance.DescribeInstances` Fetches details about Instances. diff --git a/docs/plugins/types.md b/docs/plugins/types.md index bc0c8ce00..143a6e462 100644 --- a/docs/plugins/types.md +++ b/docs/plugins/types.md @@ -71,3 +71,5 @@ Instance grouping and metadata. ### Logical ID A possibly-reused logical identifier for an Instance. +### Labels +A map or dictionary of key-value pairs that label / tag the instance. Same as [Instance Tags](#instance-tags) \ No newline at end of file From b27dd14b8f29388c673a39cdaf55ca2fb81b16d3 Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 26 Jan 2017 23:42:29 -0800 Subject: [PATCH 06/10] terraform label implementation Signed-off-by: David Chung --- examples/instance/terraform/plugin.go | 54 ++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/examples/instance/terraform/plugin.go b/examples/instance/terraform/plugin.go index 635bce5c1..4385d4611 100644 --- a/examples/instance/terraform/plugin.go +++ b/examples/instance/terraform/plugin.go @@ -428,7 +428,59 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { // Label labels the instance func (p *plugin) Label(instance instance.ID, labels map[string]string) error { - return fmt.Errorf("not implemented") + buff, err := afero.ReadFile(p.fs, filepath.Join(p.Dir, string(instance)+".tf.json")) + if err != nil { + return err + } + + tfFile := map[string]interface{}{} + err = json.Unmarshal(buff, &tfFile) + if err != nil { + return err + } + + resources, has := tfFile["resource"].(map[string]interface{}) + if !has { + return fmt.Errorf("bad tfile:%v", instance) + } + + var first map[string]interface{} // there should be only one element keyed by the type (e.g. aws_instance) + for _, r := range resources { + if f, ok := r.(map[string]interface{}); ok { + first = f + break + } + } + + if len(first) == 0 { + return fmt.Errorf("no typed properties:%v", instance) + } + + props, has := first[string(instance)].(map[string]interface{}) + if !has { + return fmt.Errorf("not found:%v", instance) + } + + // update props.tags + if _, has := props["tags"]; !has { + props["tags"] = map[string]interface{}{} + } + + if tags, ok := props["tags"].(map[string]interface{}); ok { + for k, v := range labels { + tags[k] = v + } + } + + buff, err = json.MarshalIndent(tfFile, " ", " ") + if err != nil { + return err + } + err = afero.WriteFile(p.fs, filepath.Join(p.Dir, string(instance)+".tf.json"), buff, 0644) + if err != nil { + return err + } + return p.terraformApply() } // Destroy terminates an existing instance. From 021d56c838d5ccfb58043768c72a822338330d43 Mon Sep 17 00:00:00 2001 From: David Chung Date: Sat, 28 Jan 2017 12:09:34 -0800 Subject: [PATCH 07/10] tests for terraform Signed-off-by: David Chung --- examples/instance/terraform/plugin.go | 62 ++++++-- examples/instance/terraform/plugin_test.go | 174 +++++++++++++++++++++ 2 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 examples/instance/terraform/plugin_test.go diff --git a/examples/instance/terraform/plugin.go b/examples/instance/terraform/plugin.go index 4385d4611..134e6779c 100644 --- a/examples/instance/terraform/plugin.go +++ b/examples/instance/terraform/plugin.go @@ -35,6 +35,7 @@ type plugin struct { lock lockfile.Lockfile applying bool applyLock sync.Mutex + pretend bool // true to actually do terraform apply } // NewTerraformInstancePlugin returns an instance plugin backed by disk files. @@ -168,6 +169,10 @@ func addUserData(m map[string]interface{}, key string, init string) { } func (p *plugin) terraformApply() error { + if p.pretend { + return nil + } + p.applyLock.Lock() defer p.applyLock.Unlock() @@ -179,7 +184,7 @@ func (p *plugin) terraformApply() error { for { if err := p.lock.TryLock(); err == nil { defer p.lock.Unlock() - p.doTerraformApply() + doTerraformApply(p.Dir) } log.Debugln("Can't acquire lock, waiting") time.Sleep(time.Duration(int64(rand.NormFloat64())%1000) * time.Millisecond) @@ -189,10 +194,10 @@ func (p *plugin) terraformApply() error { return nil } -func (p *plugin) doTerraformApply() error { +func doTerraformApply(dir string) error { log.Infoln(time.Now().Format(time.RFC850) + " Applying plan") cmd := exec.Command("terraform", "apply") - cmd.Dir = p.Dir + cmd.Dir = dir stdout, err := cmd.StdoutPipe() if err != nil { return err @@ -444,10 +449,12 @@ func (p *plugin) Label(instance instance.ID, labels map[string]string) error { return fmt.Errorf("bad tfile:%v", instance) } + var resourceType string var first map[string]interface{} // there should be only one element keyed by the type (e.g. aws_instance) - for _, r := range resources { + for k, r := range resources { if f, ok := r.(map[string]interface{}); ok { first = f + resourceType = k break } } @@ -461,15 +468,50 @@ func (p *plugin) Label(instance instance.ID, labels map[string]string) error { return fmt.Errorf("not found:%v", instance) } - // update props.tags - if _, has := props["tags"]; !has { - props["tags"] = map[string]interface{}{} - } + switch resourceType { + case "aws_instance", "azurerm_virtual_machine", "digitalocean_droplet", "google_compute_instance": + if _, has := props["tags"]; !has { + props["tags"] = map[string]interface{}{} + } - if tags, ok := props["tags"].(map[string]interface{}); ok { + if tags, ok := props["tags"].(map[string]interface{}); ok { + for k, v := range labels { + tags[k] = v + } + } + + case "softlayer_virtual_guest": + if _, has := props["tags"]; !has { + props["tags"] = []interface{}{} + } + tags, ok := props["tags"].([]interface{}) + if !ok { + return fmt.Errorf("bad format:%v", instance) + } + + m := map[string]string{} + for _, l := range tags { + + line := fmt.Sprintf("%v", l) + if i := strings.Index(line, ":"); i > 0 { + key := line[0:i] + value := "" + if i+1 < len(line) { + value = line[i+1:] + } + m[key] = value + } + } for k, v := range labels { - tags[k] = v + m[k] = v + } + + // now set the final format + lines := []string{} + for k, v := range m { + lines = append(lines, fmt.Sprintf("%v:%v", k, v)) } + props["tags"] = lines } buff, err = json.MarshalIndent(tfFile, " ", " ") diff --git a/examples/instance/terraform/plugin_test.go b/examples/instance/terraform/plugin_test.go new file mode 100644 index 000000000..4dc9f1bda --- /dev/null +++ b/examples/instance/terraform/plugin_test.go @@ -0,0 +1,174 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + "sort" + "testing" + + "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestUsage1(t *testing.T) { + run(t, "softlayer_virtual_guest", ` +{ + "type": "softlayer_virtual_guest", + "value": { + "cores": 2, + "memory": 2048, + "tags": [ + "terraform_demo_swarm_mgr_sl" + ], + "connection": { + "user": "root", + "private_key": "${file(\"~/.ssh/id_rsa_de\")}" + }, + "hourly_billing": true, + "local_disk": true, + "network_speed": 100, + "datacenter": "dal10", + "os_reference_code": "UBUNTU_14_64", + "domain": "softlayer.com", + "ssh_key_ids": [ + "${data.softlayer_ssh_key.public_key.id}" + ] + } + } +`) + + run(t, "aws_instance", ` +{ + "type" : "aws_instance", + "value" : { + "ami" : "${lookup(var.aws_amis, var.aws_region)}", + "instance_type" : "m1.small", + "key_name": "PUBKEY", + "vpc_security_group_ids" : ["${aws_security_group.default.id}"], + "subnet_id": "${aws_subnet.default.id}", + "tags" : { + "Name" : "web4", + "InstancePlugin" : "terraform" + }, + "connection" : { + "user" : "ubuntu" + }, + "provisioner" : { + "remote_exec" : { + "inline" : [ + "sudo apt-get -y update", + "sudo apt-get -y install nginx", + "sudo service nginx start" + ] + } + } + } +}`) +} + +func run(t *testing.T, resourceType, properties string) { + dir, err := ioutil.TempDir("", "infrakit-instance-terraform") + require.NoError(t, err) + + defer os.RemoveAll(dir) + + terraform := NewTerraformInstancePlugin(dir) + terraform.(*plugin).pretend = true // turn off actually calling terraform + + config := types.AnyString(properties) + + err = terraform.Validate(config) + require.NoError(t, err) + + instanceSpec := instance.Spec{ + Properties: config, + Tags: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + Init: "apt-get update -y\n\napt-get install -y software", + Attachments: []instance.Attachment{ + { + ID: "ebs1", + Type: "ebs", + }, + }, + } + + id, err := terraform.Provision(instanceSpec) + require.NoError(t, err) + + tfPath := filepath.Join(dir, string(*id)+".tf.json") + buff, err := ioutil.ReadFile(tfPath) + require.NoError(t, err) + + any := types.AnyBytes(buff) + parsed := TFormat{} + err = any.Decode(&parsed) + require.NoError(t, err) + require.NotNil(t, parsed.Resource) + + props := parsed.Resource[resourceType][string(*id)] + + switch resourceType { + case "softlayer_virtual_guest": + require.Equal(t, conv([]interface{}{ + "label1:value1", + "label2:value2", + "Name:" + string(*id), + }), conv(props["tags"].([]interface{}))) + require.Equal(t, instanceSpec.Init, props["user_metadata"]) + case "aws_instance": + require.Equal(t, map[string]interface{}{ + "InstancePlugin": "terraform", + "label1": "value1", + "label2": "value2", + "Name": string(*id), + }, props["tags"]) + require.Equal(t, instanceSpec.Init, props["user_data"]) + } + + // label resources + err = terraform.Label(*id, map[string]string{ + "label1": "changed1", + "label3": "value3", + }) + + buff, err = ioutil.ReadFile(tfPath) + require.NoError(t, err) + + any = types.AnyBytes(buff) + parsed = TFormat{} + err = any.Decode(&parsed) + require.NoError(t, err) + + props = parsed.Resource[resourceType][string(*id)] + switch resourceType { + case "softlayer_virtual_guest": + require.Equal(t, conv([]interface{}{ + "label1:changed1", + "label2:value2", + "label3:value3", + "Name:" + string(*id), + }), conv(props["tags"].([]interface{}))) + case "aws_instance": + require.Equal(t, map[string]interface{}{ + "InstancePlugin": "terraform", + "label1": "changed1", + "label2": "value2", + "label3": "value3", + "Name": string(*id), + }, props["tags"]) + } +} + +func conv(a []interface{}) []string { + sa := make([]string, len(a)) + for i, x := range a { + sa[i] = x.(string) + } + sort.Strings(sa) + return sa +} From e7a35c891da7d22bc543463cecebd4751d36cd38 Mon Sep 17 00:00:00 2001 From: David Chung Date: Sat, 28 Jan 2017 12:31:00 -0800 Subject: [PATCH 08/10] code refactor Signed-off-by: David Chung --- examples/instance/terraform/plugin.go | 38 +++++----------------- examples/instance/terraform/plugin_test.go | 2 ++ examples/instance/terraform/softlayer.go | 38 ++++++++++++++++++++++ 3 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 examples/instance/terraform/softlayer.go diff --git a/examples/instance/terraform/plugin.go b/examples/instance/terraform/plugin.go index 134e6779c..4c935b611 100644 --- a/examples/instance/terraform/plugin.go +++ b/examples/instance/terraform/plugin.go @@ -372,14 +372,15 @@ func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) { case "softlayer_virtual_guest": log.Debugln("softlayer_virtual_guest detected, adding hostname to properties: hostname=", name) properties.Value["hostname"] = name - var tags []interface{} - //softlayer uses a list of tags, instead of a map of tags - for i, v := range spec.Tags { - log.Debugln("softlayer_virtual_guest detected, append system tag v=", v) - tags = append(tags, i+":"+v) + if _, has := properties.Value["tags"]; !has { + properties.Value["tags"] = []interface{}{} + } + tags, ok := properties.Value["tags"].([]interface{}) + if ok { + //softlayer uses a list of tags, instead of a map of tags + properties.Value["tags"] = mergeLabelsIntoTagSlice(tags, spec.Tags) } - properties.Value["tags"] = tags } // Use tag to store the logical id @@ -488,30 +489,7 @@ func (p *plugin) Label(instance instance.ID, labels map[string]string) error { if !ok { return fmt.Errorf("bad format:%v", instance) } - - m := map[string]string{} - for _, l := range tags { - - line := fmt.Sprintf("%v", l) - if i := strings.Index(line, ":"); i > 0 { - key := line[0:i] - value := "" - if i+1 < len(line) { - value = line[i+1:] - } - m[key] = value - } - } - for k, v := range labels { - m[k] = v - } - - // now set the final format - lines := []string{} - for k, v := range m { - lines = append(lines, fmt.Sprintf("%v:%v", k, v)) - } - props["tags"] = lines + props["tags"] = mergeLabelsIntoTagSlice(tags, labels) } buff, err = json.MarshalIndent(tfFile, " ", " ") diff --git a/examples/instance/terraform/plugin_test.go b/examples/instance/terraform/plugin_test.go index 4dc9f1bda..003239781 100644 --- a/examples/instance/terraform/plugin_test.go +++ b/examples/instance/terraform/plugin_test.go @@ -115,6 +115,7 @@ func run(t *testing.T, resourceType, properties string) { switch resourceType { case "softlayer_virtual_guest": require.Equal(t, conv([]interface{}{ + "terraform_demo_swarm_mgr_sl", "label1:value1", "label2:value2", "Name:" + string(*id), @@ -148,6 +149,7 @@ func run(t *testing.T, resourceType, properties string) { switch resourceType { case "softlayer_virtual_guest": require.Equal(t, conv([]interface{}{ + "terraform_demo_swarm_mgr_sl", "label1:changed1", "label2:value2", "label3:value3", diff --git a/examples/instance/terraform/softlayer.go b/examples/instance/terraform/softlayer.go new file mode 100644 index 000000000..4eb5221be --- /dev/null +++ b/examples/instance/terraform/softlayer.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "strings" +) + +func mergeLabelsIntoTagSlice(tags []interface{}, labels map[string]string) []string { + m := map[string]string{} + for _, l := range tags { + line := fmt.Sprintf("%v", l) // conversion using string + if i := strings.Index(line, ":"); i > 0 { + key := line[0:i] + value := "" + if i+1 < len(line) { + value = line[i+1:] + } + m[key] = value + } else { + m[fmt.Sprintf("%v", l)] = "" + } + } + for k, v := range labels { + m[k] = v + } + + // now set the final format + lines := []string{} + for k, v := range m { + if v != "" { + lines = append(lines, fmt.Sprintf("%v:%v", k, v)) + } else { + lines = append(lines, k) + + } + } + return lines +} From 42893b75b28a553bd9a5340d04e5e59f4a810eab Mon Sep 17 00:00:00 2001 From: David Chung Date: Sat, 28 Jan 2017 13:05:50 -0800 Subject: [PATCH 09/10] more tests Signed-off-by: David Chung --- examples/instance/file/plugin.go | 2 +- examples/instance/file/plugin_test.go | 89 ++++++++++++++++++++++ examples/instance/terraform/plugin_test.go | 2 +- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 examples/instance/file/plugin_test.go diff --git a/examples/instance/file/plugin.go b/examples/instance/file/plugin.go index 0d86f61ec..2e081ceb8 100644 --- a/examples/instance/file/plugin.go +++ b/examples/instance/file/plugin.go @@ -53,7 +53,7 @@ func (p *plugin) VendorInfo() *spi.VendorInfo { return &spi.VendorInfo{ InterfaceSpec: spi.InterfaceSpec{ Name: "infrakit-instance-file", - Version: "0.1.0", + Version: "0.3.0", }, URL: "https://github.com/docker/infrakit", } diff --git a/examples/instance/file/plugin_test.go b/examples/instance/file/plugin_test.go new file mode 100644 index 000000000..e9f268ba6 --- /dev/null +++ b/examples/instance/file/plugin_test.go @@ -0,0 +1,89 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/docker/infrakit/pkg/spi/instance" + "github.com/docker/infrakit/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestUsage(t *testing.T) { + run(t, ` +{ + "ami" : "${lookup(var.aws_amis, var.aws_region)}", + "instance_type" : "m1.small", + "key_name": "PUBKEY", + "vpc_security_group_ids" : ["${aws_security_group.default.id}"], + "subnet_id": "${aws_subnet.default.id}" +}`) +} + +func run(t *testing.T, properties string) { + dir, err := ioutil.TempDir("", "infrakit-instance-file") + require.NoError(t, err) + + defer os.RemoveAll(dir) + + fileinst := NewFileInstancePlugin(dir) + + config := types.AnyString(properties) + + err = fileinst.Validate(config) + require.NoError(t, err) + + instanceSpec := instance.Spec{ + Properties: config, + Tags: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + Init: "apt-get update -y\n\napt-get install -y software", + Attachments: []instance.Attachment{ + { + ID: "ebs1", + Type: "ebs", + }, + }, + } + + id, err := fileinst.Provision(instanceSpec) + require.NoError(t, err) + + tfPath := filepath.Join(dir, string(*id)) + buff, err := ioutil.ReadFile(tfPath) + require.NoError(t, err) + + any := types.AnyBytes(buff) + parsed := fileInstance{} + err = any.Decode(&parsed) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "label1": "value1", + "label2": "value2", + }, parsed.Description.Tags) + require.Equal(t, instanceSpec.Init, parsed.Spec.Init) + + // label resources + err = fileinst.Label(*id, map[string]string{ + "label1": "changed1", + "label3": "value3", + }) + + buff, err = ioutil.ReadFile(tfPath) + require.NoError(t, err) + + any = types.AnyBytes(buff) + parsed = fileInstance{} + err = any.Decode(&parsed) + require.NoError(t, err) + + require.Equal(t, map[string]string{ + "label1": "changed1", + "label2": "value2", + "label3": "value3", + }, parsed.Description.Tags) +} diff --git a/examples/instance/terraform/plugin_test.go b/examples/instance/terraform/plugin_test.go index 003239781..009aefa98 100644 --- a/examples/instance/terraform/plugin_test.go +++ b/examples/instance/terraform/plugin_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestUsage1(t *testing.T) { +func TestUsage(t *testing.T) { run(t, "softlayer_virtual_guest", ` { "type": "softlayer_virtual_guest", From 899b7902afc1bd3673ae63acbcb297c384baea24 Mon Sep 17 00:00:00 2001 From: David Chung Date: Sun, 29 Jan 2017 18:37:00 -0800 Subject: [PATCH 10/10] more tests Signed-off-by: David Chung --- examples/instance/file/plugin_test.go | 17 ++++++ examples/instance/terraform/plugin.go | 2 +- examples/instance/terraform/plugin_test.go | 39 ++++++++++++++ pkg/rpc/instance/client.go | 8 +-- pkg/rpc/instance/rpc_multi_test.go | 48 +++++++++++++++++ pkg/rpc/instance/rpc_test.go | 60 ++++++++++++++++++++++ 6 files changed, 167 insertions(+), 7 deletions(-) diff --git a/examples/instance/file/plugin_test.go b/examples/instance/file/plugin_test.go index e9f268ba6..25d0a5d20 100644 --- a/examples/instance/file/plugin_test.go +++ b/examples/instance/file/plugin_test.go @@ -86,4 +86,21 @@ func run(t *testing.T, properties string) { "label2": "value2", "label3": "value3", }, parsed.Description.Tags) + + list, err := fileinst.DescribeInstances(map[string]string{"label1": "changed1"}) + require.NoError(t, err) + require.Equal(t, []instance.Description{ + { + ID: *id, + Tags: parsed.Description.Tags, + }, + }, list) + + err = fileinst.Destroy(*id) + require.NoError(t, err) + + list, err = fileinst.DescribeInstances(map[string]string{"label1": "changed1"}) + require.NoError(t, err) + require.Equal(t, []instance.Description{}, list) + } diff --git a/examples/instance/terraform/plugin.go b/examples/instance/terraform/plugin.go index 4c935b611..baf0b2571 100644 --- a/examples/instance/terraform/plugin.go +++ b/examples/instance/terraform/plugin.go @@ -581,7 +581,7 @@ func terraformTags(v interface{}, key string) map[string]string { log.Errorln("terraformTags: ignore invalid tag detected", value) } } else { - log.Warnln("terraformTags user tags ignored v=", value) + tags[value] = "" // for list but no ':" } } log.Debugln("terraformTags return tags", tags) diff --git a/examples/instance/terraform/plugin_test.go b/examples/instance/terraform/plugin_test.go index 009aefa98..88119ca63 100644 --- a/examples/instance/terraform/plugin_test.go +++ b/examples/instance/terraform/plugin_test.go @@ -164,6 +164,45 @@ func run(t *testing.T, resourceType, properties string) { "Name": string(*id), }, props["tags"]) } + + list, err := terraform.DescribeInstances(map[string]string{"label1": "changed1"}) + require.NoError(t, err) + + switch resourceType { + case "softlayer_virtual_guest": + require.Equal(t, []instance.Description{ + { + ID: *id, + Tags: map[string]string{ + "terraform_demo_swarm_mgr_sl": "", + "label1": "changed1", + "label2": "value2", + "label3": "value3", + "Name": string(*id), + }, + }, + }, list) + case "aws_instance": + require.Equal(t, []instance.Description{ + { + ID: *id, + Tags: map[string]string{ + "InstancePlugin": "terraform", + "label1": "changed1", + "label2": "value2", + "label3": "value3", + "Name": string(*id), + }, + }, + }, list) + } + + err = terraform.Destroy(*id) + require.NoError(t, err) + + list, err = terraform.DescribeInstances(map[string]string{"label1": "changed1"}) + require.NoError(t, err) + require.Equal(t, []instance.Description{}, list) } func conv(a []interface{}) []string { diff --git a/pkg/rpc/instance/client.go b/pkg/rpc/instance/client.go index b8fb9134a..e6ea49052 100644 --- a/pkg/rpc/instance/client.go +++ b/pkg/rpc/instance/client.go @@ -46,14 +46,10 @@ func (c client) Provision(spec instance.Spec) (*instance.ID, error) { // Label labels the instance func (c client) Label(instance instance.ID, labels map[string]string) error { _, instanceType := c.name.GetLookupAndType() - req := LabelRequest{Type: instanceType, Instance: instance} + req := LabelRequest{Type: instanceType, Instance: instance, Labels: labels} resp := LabelResponse{} - if err := c.client.Call("Instance.Label", req, &resp); err != nil { - return err - } - - return nil + return c.client.Call("Instance.Label", req, &resp) } // Destroy terminates an existing instance. diff --git a/pkg/rpc/instance/rpc_multi_test.go b/pkg/rpc/instance/rpc_multi_test.go index 07639a6aa..3c38b6749 100644 --- a/pkg/rpc/instance/rpc_multi_test.go +++ b/pkg/rpc/instance/rpc_multi_test.go @@ -173,6 +173,54 @@ func TestInstanceTypedPluginProvision(t *testing.T) { require.Equal(t, spec3, <-specActual3) } +func TestInstanceTypedPluginLabel(t *testing.T) { + socketPath := tempSocket() + name := filepath.Base(socketPath) + + inst1 := instance.ID("hello1") + labels1 := map[string]string{"l1": "v1"} + instActual1 := make(chan instance.ID, 1) + labelActual1 := make(chan map[string]string, 1) + + inst2 := instance.ID("hello2") + labels2 := map[string]string{"l1": "v2"} + instActual2 := make(chan instance.ID, 2) + labelActual2 := make(chan map[string]string, 1) + + server, err := rpc_server.StartPluginAtPath(socketPath, PluginServerWithTypes( + map[string]instance.Plugin{ + "type1": &testPlugin{ + DoLabel: func(req instance.ID, labels map[string]string) error { + instActual1 <- req + labelActual1 <- labels + return nil + }, + }, + "type2": &testPlugin{ + DoLabel: func(req instance.ID, labels map[string]string) error { + instActual2 <- req + labelActual2 <- labels + return errors.New("can't do") + }, + }, + })) + require.NoError(t, err) + + err = must(NewClient(plugin.Name(name+"/type1"), socketPath)).Label(inst1, labels1) + require.NoError(t, err) + + err = must(NewClient(plugin.Name(name+"/type2"), socketPath)).Label(inst2, labels2) + require.Error(t, err) + require.Equal(t, "can't do", err.Error()) + + server.Stop() + + require.Equal(t, inst1, <-instActual1) + require.Equal(t, inst2, <-instActual2) + require.Equal(t, labels1, <-labelActual1) + require.Equal(t, labels2, <-labelActual2) +} + func TestInstanceTypedPluginDestroy(t *testing.T) { socketPath := tempSocket() name := filepath.Base(socketPath) diff --git a/pkg/rpc/instance/rpc_test.go b/pkg/rpc/instance/rpc_test.go index 002a65c98..203b0ef43 100644 --- a/pkg/rpc/instance/rpc_test.go +++ b/pkg/rpc/instance/rpc_test.go @@ -189,6 +189,66 @@ func TestInstancePluginProvisionError(t *testing.T) { require.Equal(t, spec, <-specActual) } +func TestInstancePluginLabel(t *testing.T) { + socketPath := tempSocket() + name := plugin.Name(filepath.Base(socketPath)) + + inst := instance.ID("hello") + labels := map[string]string{ + "label1": "value1", + "label2": "value2", + } + instActual := make(chan instance.ID, 1) + labelActual := make(chan map[string]string, 1) + server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ + DoLabel: func(req instance.ID, labels map[string]string) error { + instActual <- req + labelActual <- labels + return nil + }, + })) + require.NoError(t, err) + + err = must(NewClient(name, socketPath)).Label(inst, labels) + require.NoError(t, err) + + server.Stop() + + require.Equal(t, inst, <-instActual) + require.Equal(t, labels, <-labelActual) +} + +func TestInstancePluginLabelError(t *testing.T) { + socketPath := tempSocket() + name := plugin.Name(filepath.Base(socketPath)) + + inst := instance.ID("hello") + labels := map[string]string{ + "label1": "value1", + "label2": "value2", + } + + instActual := make(chan instance.ID, 1) + labelActual := make(chan map[string]string, 1) + + server, err := rpc_server.StartPluginAtPath(socketPath, PluginServer(&testPlugin{ + DoLabel: func(req instance.ID, labels map[string]string) error { + instActual <- req + labelActual <- labels + return errors.New("can't do") + }, + })) + require.NoError(t, err) + + err = must(NewClient(name, socketPath)).Label(inst, labels) + require.Error(t, err) + require.Equal(t, "can't do", err.Error()) + + server.Stop() + require.Equal(t, inst, <-instActual) + require.Equal(t, labels, <-labelActual) +} + func TestInstancePluginDestroy(t *testing.T) { socketPath := tempSocket() name := plugin.Name(filepath.Base(socketPath))