From 2af030ab57d1d84ac9a1d22552dc9d83b16951c4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 17:01:27 -0700 Subject: [PATCH 1/3] beam/data: Message.GetOne() returns the last value set at a key This is a convenience for callers which are only interested in one value per key. Similar to how HTTP headers allow multiple keys per value, but are often used to store and retrieve only one value. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- pkg/beam/data/message.go | 10 ++++++++++ pkg/beam/data/message_test.go | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/pkg/beam/data/message.go b/pkg/beam/data/message.go index 193fb7b241..0ebe90295a 100644 --- a/pkg/beam/data/message.go +++ b/pkg/beam/data/message.go @@ -72,6 +72,16 @@ func (m Message) Get(k string) []string { return v } +// GetOne returns the last value added at the key k, +// or an empty string if there is no value. +func (m Message) GetOne(k string) string { + var v string + if vals := m.Get(k); len(vals) > 0 { + v = vals[len(vals)-1] + } + return v +} + func (m Message) Pretty() string { data, err := Decode(string(m)) if err != nil { diff --git a/pkg/beam/data/message_test.go b/pkg/beam/data/message_test.go index 7685769069..7224f33d11 100644 --- a/pkg/beam/data/message_test.go +++ b/pkg/beam/data/message_test.go @@ -51,3 +51,11 @@ func TestSetDelMessage(t *testing.T) { t.Fatalf("'%v' != '%v'", output, expectedOutput) } } + +func TestGetOne(t *testing.T) { + m := Empty().Set("shadok words", "ga", "bu", "zo", "meu") + val := m.GetOne("shadok words") + if val != "meu" { + t.Fatalf("%#v", val) + } +} From c7978c98097b92b659e2ea1763cd134b0695407e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 17:06:32 -0700 Subject: [PATCH 2/3] Engine: Env.MultiMap, Env.InitMultiMap: import/export to other formats * `Env.MultiMap` returns the contents of an Env as `map[string][]string` * `Env.InitMultiMap` initializes the contents of an Env from a `map[string][]string` This makes it easier to import and export an Env to other formats (specifically `beam/data` messages) Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/env.go | 24 ++++++++++++++++++++++++ engine/env_test.go | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/engine/env.go b/engine/env.go index 94d8c3d8a7..f63f29e10f 100644 --- a/engine/env.go +++ b/engine/env.go @@ -250,3 +250,27 @@ func (env *Env) Map() map[string]string { } return m } + +// MultiMap returns a representation of env as a +// map of string arrays, keyed by string. +// This is the same structure as http headers for example, +// which allow each key to have multiple values. +func (env *Env) MultiMap() map[string][]string { + m := make(map[string][]string) + for _, kv := range *env { + parts := strings.SplitN(kv, "=", 2) + m[parts[0]] = append(m[parts[0]], parts[1]) + } + return m +} + +// InitMultiMap removes all values in env, then initializes +// new values from the contents of m. +func (env *Env) InitMultiMap(m map[string][]string) { + (*env) = make([]string, 0, len(m)) + for k, vals := range m { + for _, v := range vals { + env.Set(k, v) + } + } +} diff --git a/engine/env_test.go b/engine/env_test.go index 0c66cea04e..39669d6780 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -123,3 +123,23 @@ func TestEnviron(t *testing.T) { t.Fatalf("bar not found in the environ") } } + +func TestMultiMap(t *testing.T) { + e := &Env{} + e.Set("foo", "bar") + e.Set("bar", "baz") + e.Set("hello", "world") + m := e.MultiMap() + e2 := &Env{} + e2.Set("old_key", "something something something") + e2.InitMultiMap(m) + if v := e2.Get("old_key"); v != "" { + t.Fatalf("%#v", v) + } + if v := e2.Get("bar"); v != "baz" { + t.Fatalf("%#v", v) + } + if v := e2.Get("hello"); v != "world" { + t.Fatalf("%#v", v) + } +} From a1754c7e46b321c2a079fc5186984a847aa50218 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 17:10:33 -0700 Subject: [PATCH 3/3] Engine: Receiver and Sender preserve Job.Env When sending a new job to a `engine.Sender`, the corresponding `engine.Receiver` will receive that job with its environment preserved. Previously the job name, arguments and streams were preserved but the env was lost. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 13 +++++++++++-- engine/remote_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/engine/remote.go b/engine/remote.go index 1da521a3c7..974ca02137 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -25,7 +25,9 @@ func (s *Sender) Install(eng *Engine) error { } func (s *Sender) Handle(job *Job) Status { - msg := data.Empty().Set("cmd", append([]string{job.Name}, job.Args...)...) + cmd := append([]string{job.Name}, job.Args...) + env := data.Encode(job.Env().MultiMap()) + msg := data.Empty().Set("cmd", cmd...).Set("env", env) peer, err := beam.SendConn(s, msg.Bytes()) if err != nil { return job.Errorf("beamsend: %v", err) @@ -99,8 +101,15 @@ func (rcv *Receiver) Run() error { } f.Close() defer peer.Close() - cmd := data.Message(p).Get("cmd") + msg := data.Message(p) + cmd := msg.Get("cmd") job := rcv.Engine.Job(cmd[0], cmd[1:]...) + // Decode env + env, err := data.Decode(msg.GetOne("env")) + if err != nil { + return fmt.Errorf("error decoding 'env': %v", err) + } + job.Env().InitMultiMap(env) stdout, err := beam.SendRPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) if err != nil { return err diff --git a/engine/remote_test.go b/engine/remote_test.go index a23830af01..e59ac78cc0 100644 --- a/engine/remote_test.go +++ b/engine/remote_test.go @@ -82,6 +82,47 @@ func TestStdin(t *testing.T) { ) } +func TestEnv(t *testing.T) { + var ( + foo string + answer int + shadok_words []string + ) + testRemote(t, + + func(eng *Engine) { + job := eng.Job("sendenv") + job.Env().Set("foo", "bar") + job.Env().SetInt("answer", 42) + job.Env().SetList("shadok_words", []string{"ga", "bu", "zo", "meu"}) + if err := job.Run(); err != nil { + t.Fatal(err) + } + }, + + func(eng *Engine) { + eng.Register("sendenv", func(job *Job) Status { + foo = job.Env().Get("foo") + answer = job.Env().GetInt("answer") + shadok_words = job.Env().GetList("shadok_words") + return StatusOK + }) + }, + ) + // Check for results here rather than inside the job handler, + // otherwise the tests may incorrectly pass if the handler is not + // called. + if foo != "bar" { + t.Fatalf("%#v", foo) + } + if answer != 42 { + t.Fatalf("%#v", answer) + } + if strings.Join(shadok_words, ", ") != "ga, bu, zo, meu" { + t.Fatalf("%#v", shadok_words) + } +} + // Helpers func testRemote(t *testing.T, senderSide, receiverSide func(*Engine)) {