From 17137ba3e7037b9b27d8eb1ddb1cf87e842f8aa3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 10 Jan 2018 04:05:33 +0100 Subject: [PATCH] refactor(mesos) be testable. --- provider/mesos/config.go | 69 ++-- provider/mesos/config_test.go | 472 +++++++++++++++++----------- provider/mesos/mesos.go | 41 ++- provider/mesos/mesos_helper_test.go | 186 ++++++----- 4 files changed, 466 insertions(+), 302 deletions(-) diff --git a/provider/mesos/config.go b/provider/mesos/config.go index 87a739698..1d6069840 100644 --- a/provider/mesos/config.go +++ b/provider/mesos/config.go @@ -6,56 +6,48 @@ import ( "strconv" "strings" "text/template" - "time" "github.com/BurntSushi/ty/fun" "github.com/containous/traefik/log" "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" - "github.com/mesosphere/mesos-dns/records" "github.com/mesosphere/mesos-dns/records/state" ) -func (p *Provider) buildConfiguration() *types.Configuration { +func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration { var mesosFuncMap = template.FuncMap{ - "getBackend": getBackend, - "getPort": p.getPort, - "getHost": p.getHost, - "getWeight": getFuncApplicationStringValue(label.TraefikWeight, label.DefaultWeight), - "getDomain": getFuncStringValue(label.TraefikDomain, p.Domain), - "getProtocol": getFuncApplicationStringValue(label.TraefikProtocol, label.DefaultProtocol), + "getDomain": getFuncStringValue(label.TraefikDomain, p.Domain), + "getID": getID, + + // Backend functions + "getProtocol": getFuncApplicationStringValue(label.TraefikProtocol, label.DefaultProtocol), + "getPort": p.getPort, + "getHost": p.getHost, + "getWeight": getFuncApplicationStringValue(label.TraefikWeight, label.DefaultWeight), + "getBackend": getBackend, + + // Frontend functions "getPassHostHeader": getFuncStringValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), "getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority), "getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints), "getFrontendRule": p.getFrontendRule, "getFrontendBackend": getFrontendBackend, - "getID": getID, "getFrontEndName": getFrontEndName, } - rg := records.NewRecordGenerator(time.Duration(p.StateTimeoutSecond) * time.Second) - st, err := rg.FindMaster(p.Masters...) - if err != nil { - log.Errorf("Failed to create a client for Mesos, error: %v", err) - return nil - } - tasks := taskRecords(st) - // filter tasks filteredTasks := fun.Filter(func(task state.Task) bool { return taskFilter(task, p.ExposedByDefault) }, tasks).([]state.Task) - uniqueApps := make(map[string]state.Task) - for _, value := range filteredTasks { - if _, ok := uniqueApps[value.DiscoveryInfo.Name]; !ok { - uniqueApps[value.DiscoveryInfo.Name] = value - } - } var filteredApps []state.Task - for _, value := range uniqueApps { - filteredApps = append(filteredApps, value) + uniqueApps := make(map[string]struct{}) + for _, task := range filteredTasks { + if _, ok := uniqueApps[task.DiscoveryInfo.Name]; !ok { + uniqueApps[task.DiscoveryInfo.Name] = struct{}{} + filteredApps = append(filteredApps, task) + } } templateObjects := struct { @@ -75,31 +67,12 @@ func (p *Provider) buildConfiguration() *types.Configuration { return configuration } -func taskRecords(st state.State) []state.Task { - var tasks []state.Task - for _, f := range st.Frameworks { - for _, task := range f.Tasks { - for _, slave := range st.Slaves { - if task.SlaveID == slave.ID { - task.SlaveIP = slave.PID.Host - } - } - - // only do running and discoverable tasks - if task.State == "TASK_RUNNING" { - tasks = append(tasks, task) - } - } - } - - return tasks -} - func taskFilter(task state.Task, exposedByDefaultFlag bool) bool { if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 { log.Debugf("Filtering Mesos task without port %s", task.Name) return false } + if !isEnabled(task, exposedByDefaultFlag) { log.Debugf("Filtering disabled Mesos task %s", task.DiscoveryInfo.Name) return false @@ -140,7 +113,7 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool { } } - //filter healthChecks + // filter healthChecks if task.Statuses != nil && len(task.Statuses) > 0 && task.Statuses[0].Healthy != nil && !*task.Statuses[0].Healthy { log.Debugf("Filtering Mesos task %s with bad healthCheck", task.DiscoveryInfo.Name) return false @@ -166,7 +139,7 @@ func getFrontendBackend(task state.Task) string { if value := getStringValue(task, label.TraefikBackend, ""); len(value) > 0 { return value } - return "-" + provider.Normalize(task.DiscoveryInfo.Name) + return provider.Normalize(task.DiscoveryInfo.Name) } func getFrontEndName(task state.Task) string { diff --git a/provider/mesos/config_test.go b/provider/mesos/config_test.go index e03a35670..0a03099de 100644 --- a/provider/mesos/config_test.go +++ b/provider/mesos/config_test.go @@ -1,8 +1,6 @@ package mesos import ( - "reflect" - "strconv" "testing" "github.com/containous/traefik/provider/label" @@ -10,238 +8,362 @@ import ( "github.com/mesos/mesos-go/upid" "github.com/mesosphere/mesos-dns/records/state" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -// FIXME fill this test!! func TestBuildConfiguration(t *testing.T) { - cases := []struct { - applicationsError bool - tasksError bool - mesosTask state.Task - expected bool - exposedByDefault bool - expectedNil bool + p := &Provider{ + Domain: "docker.localhost", + ExposedByDefault: true, + IPSources: "host", + } + + testCases := []struct { + desc string + tasks []state.Task expectedFrontends map[string]*types.Frontend expectedBackends map[string]*types.Backend - }{} + }{ + { + desc: "should return an empty configuration when no task", + tasks: []state.Task{}, + expectedFrontends: map[string]*types.Frontend{}, + expectedBackends: map[string]*types.Backend{}, + }, + { + desc: "2 applications with 2 tasks", + tasks: []state.Task{ + // App 1 + aTask("ID1", + withIP("10.10.10.10"), + withInfo("name1", + withPorts(withPort("TCP", 80, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + aTask("ID2", + withIP("10.10.10.11"), + withInfo("name1", + withPorts(withPort("TCP", 81, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + // App 2 + aTask("ID3", + withIP("20.10.10.10"), + withInfo("name2", + withPorts(withPort("TCP", 80, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + aTask("ID4", + withIP("20.10.10.11"), + withInfo("name2", + withPorts(withPort("TCP", 81, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-ID1": { + Backend: "backend-name1", + EntryPoints: []string{}, + PassHostHeader: true, + Routes: map[string]types.Route{ + "route-host-ID1": { + Rule: "Host:name1.docker.localhost", + }, + }, + }, + "frontend-ID3": { + Backend: "backend-name2", + EntryPoints: []string{}, + PassHostHeader: true, + Routes: map[string]types.Route{ + "route-host-ID3": { + Rule: "Host:name2.docker.localhost", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-name1": { + Servers: map[string]types.Server{ + "server-ID1": { + URL: "http://10.10.10.10:80", + Weight: 0, + }, + "server-ID2": { + URL: "http://10.10.10.11:81", + Weight: 0, + }, + }, + }, + "backend-name2": { + Servers: map[string]types.Server{ + "server-ID3": { + URL: "http://20.10.10.10:80", + Weight: 0, + }, + "server-ID4": { + URL: "http://20.10.10.11:81", + Weight: 0, + }, + }, + }, + }, + }, + { + desc: "with all labels", + tasks: []state.Task{ + aTask("ID1", + withLabel(label.TraefikPort, "666"), + withLabel(label.TraefikProtocol, "https"), + withLabel(label.TraefikWeight, "12"), - for _, c := range cases { - provider := &Provider{ - Domain: "docker.localhost", - ExposedByDefault: true, - } - actualConfig := provider.buildConfiguration() - if c.expectedNil { - if actualConfig != nil { - t.Fatalf("Should have been nil, got %v", actualConfig) - } - } else { - // Compare backends - if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { - t.Fatalf("Expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) - } - if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { - t.Fatalf("Expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) - } - } + withLabel(label.TraefikBackend, "foobar"), + + withLabel(label.TraefikFrontendEntryPoints, "http,https"), + withLabel(label.TraefikFrontendPassHostHeader, "true"), + withLabel(label.TraefikFrontendPassTLSCert, "true"), + withLabel(label.TraefikFrontendPriority, "666"), + withLabel(label.TraefikFrontendRule, "Host:traefik.io"), + + withIP("10.10.10.10"), + withInfo("name1", withPorts( + withPortTCP(80, "n"), + withPortTCP(666, "n"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-ID1": { + EntryPoints: []string{ + "http", + "https", + }, + Backend: "backend-foobar", + Routes: map[string]types.Route{ + "route-host-ID1": { + Rule: "Host:traefik.io", + }, + }, + PassHostHeader: true, + Priority: 666, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foobar": { + Servers: map[string]types.Server{ + "server-ID1": { + URL: "https://10.10.10.10:666", + Weight: 12, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + + actualConfig := p.buildConfiguration(test.tasks) + + require.NotNil(t, actualConfig) + assert.Equal(t, test.expectedBackends, actualConfig.Backends) + assert.Equal(t, test.expectedFrontends, actualConfig.Frontends) + }) } } func TestTaskFilter(t *testing.T) { testCases := []struct { + desc string mesosTask state.Task - expected bool exposedByDefault bool + expected bool }{ { + desc: "no task", mesosTask: state.Task{}, + exposedByDefault: true, expected: false, - exposedByDefault: true, }, { - mesosTask: task(statuses(status(setState("TASK_RUNNING")))), + desc: "task not healthy", + mesosTask: aTask("test", withStatus(withState("TASK_RUNNING"))), + exposedByDefault: true, expected: false, - exposedByDefault: true, }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "false"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + desc: "exposedByDefault false and traefik.enable false", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "false"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: false, // because label traefik.enable = false exposedByDefault: false, + expected: false, }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + desc: "traefik.enable = true", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: true, exposedByDefault: false, - }, - { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), - ), expected: true, - exposedByDefault: true, }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "false"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + desc: "exposedByDefault true and traefik.enable true", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: false, // because label traefik.enable = false (even wherek exposedByDefault = true) exposedByDefault: true, - }, - { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPortIndex, "1", - label.TraefikPort, "80"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), - ), - expected: false, // traefik.portIndex & traefik.port cannot be set both - exposedByDefault: true, - }, - { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPortIndex, "1"), - discovery(setDiscoveryPorts("TCP", 80, "WEB HTTP", "TCP", 443, "WEB HTTPS")), - ), expected: true, - exposedByDefault: true, }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true"), - discovery(setDiscoveryPorts("TCP", 80, "WEB HTTP", "TCP", 443, "WEB HTTPS")), + desc: "exposedByDefault true and traefik.enable false", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "false"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: true, // Default to first index exposedByDefault: true, + expected: false, }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPortIndex, "1"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + desc: "traefik.portIndex and traefik.port both set", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "1"), + withLabel(label.TraefikEnable, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: false, // traefik.portIndex and discoveryPorts don't correspond exposedByDefault: true, - }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPortIndex, "0"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + expected: false, + }, + { + desc: "valid traefik.portIndex", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "1"), + withInfo("test", withPorts( + withPortTCP(80, "WEB"), + withPortTCP(443, "WEB HTTPS"), + )), ), - expected: true, // traefik.portIndex and discoveryPorts correspond exposedByDefault: true, - }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPort, "TRAEFIK"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + expected: true, + }, + { + desc: "default to first port index", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withInfo("test", withPorts( + withPortTCP(80, "WEB"), + withPortTCP(443, "WEB HTTPS"), + )), ), - expected: false, // traefik.port is not an integer exposedByDefault: true, - }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPort, "443"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + expected: true, + }, + { + desc: "traefik.portIndex and discoveryPorts don't correspond", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "1"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: false, // traefik.port is not the same as discovery.port exposedByDefault: true, - }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(true))), - setLabels(label.TraefikEnable, "true", - label.TraefikPort, "80"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + expected: false, + }, + { + desc: "traefik.portIndex and discoveryPorts correspond", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "0"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: true, // traefik.port is the same as discovery.port exposedByDefault: true, - }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"))), - setLabels(label.TraefikEnable, "true", - label.TraefikPort, "80"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + expected: true, + }, + { + desc: "traefik.port is not an integer", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "TRAEFIK"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: true, // No healthCheck exposedByDefault: true, - }, { - mesosTask: task( - statuses( - status( - setState("TASK_RUNNING"), - setHealthy(false))), - setLabels(label.TraefikEnable, "true", - label.TraefikPort, "80"), - discovery(setDiscoveryPort("TCP", 80, "WEB")), + expected: false, + }, + { + desc: "traefik.port is not the same as discovery.port", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "443"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), ), - expected: false, // HealthCheck at false exposedByDefault: true, + expected: false, + }, + { + desc: "traefik.port is the same as discovery.port", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "healthy nil", + mesosTask: aTask("test", + withStatus( + withState("TASK_RUNNING"), + ), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "healthy false", + mesosTask: aTask("test", + withStatus( + withState("TASK_RUNNING"), + withHealthy(false), + ), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, }, } - for index, test := range testCases { - t.Run(strconv.Itoa(index), func(t *testing.T) { + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { t.Parallel() actual := taskFilter(test.mesosTask, test.exposedByDefault) - if actual != test.expected { + ok := assert.Equal(t, test.expected, actual) + if !ok { t.Logf("Statuses : %v", test.mesosTask.Statuses) t.Logf("Label : %v", test.mesosTask.Labels) t.Logf("DiscoveryInfo : %v", test.mesosTask.DiscoveryInfo) @@ -303,7 +425,7 @@ func TestGetSubDomain(t *testing.T) { for _, test := range testCases { test := test - t.Run("", func(t *testing.T) { + t.Run(test.path, func(t *testing.T) { t.Parallel() actual := test.provider.getSubDomain(test.path) diff --git a/provider/mesos/mesos.go b/provider/mesos/mesos.go index 90ec9c140..6baab7de1 100644 --- a/provider/mesos/mesos.go +++ b/provider/mesos/mesos.go @@ -12,6 +12,9 @@ import ( "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/mesos/mesos-go/detector" + "github.com/mesosphere/mesos-dns/records" + "github.com/mesosphere/mesos-dns/records/state" + // Register mesos zoo the detector _ "github.com/mesos/mesos-go/detector/zoo" "github.com/mesosphere/mesos-dns/detect" @@ -76,7 +79,8 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s for { select { case <-reload.C: - configuration := p.buildConfiguration() + tasks := p.getTasks() + configuration := p.buildConfiguration(tasks) if configuration != nil { configurationChan <- types.ConfigMessage{ ProviderName: "mesos", @@ -92,7 +96,8 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s } log.Debugf("new masters detected: %v", masters) p.Masters = masters - configuration := p.buildConfiguration() + tasks := p.getTasks() + configuration := p.buildConfiguration(tasks) if configuration != nil { configurationChan <- types.ConfigMessage{ ProviderName: "mesos", @@ -129,3 +134,35 @@ func detectMasters(zk string, masters []string) <-chan []string { } return changed } + +func (p *Provider) getTasks() []state.Task { + rg := records.NewRecordGenerator(time.Duration(p.StateTimeoutSecond) * time.Second) + + st, err := rg.FindMaster(p.Masters...) + if err != nil { + log.Errorf("Failed to create a client for Mesos, error: %v", err) + return nil + } + + return taskRecords(st) +} + +func taskRecords(st state.State) []state.Task { + var tasks []state.Task + for _, f := range st.Frameworks { + for _, task := range f.Tasks { + for _, slave := range st.Slaves { + if task.SlaveID == slave.ID { + task.SlaveIP = slave.PID.Host + } + } + + // only do running and discoverable tasks + if task.State == "TASK_RUNNING" { + tasks = append(tasks, task) + } + } + } + + return tasks +} diff --git a/provider/mesos/mesos_helper_test.go b/provider/mesos/mesos_helper_test.go index c9aac47d5..f7ac6353a 100644 --- a/provider/mesos/mesos_helper_test.go +++ b/provider/mesos/mesos_helper_test.go @@ -1,113 +1,145 @@ package mesos import ( - "github.com/containous/traefik/log" + "testing" + "github.com/mesosphere/mesos-dns/records/state" + "github.com/stretchr/testify/assert" ) // test helpers -type ( - taskOpt func(*state.Task) - statusOpt func(*state.Status) -) +func TestBuilder(t *testing.T) { + result := aTask("ID1", + withIP("10.10.10.10"), + withLabel("foo", "bar"), + withLabel("fii", "bar"), + withLabel("fuu", "bar"), + withInfo("name1", + withPorts(withPort("TCP", 80, "p"), + withPortTCP(81, "n"))), + withStatus(withHealthy(true), withState("a"))) -func task(opts ...taskOpt) state.Task { - var t state.Task - for _, opt := range opts { - opt(&t) - } - return t + expected := state.Task{ + FrameworkID: "", + ID: "ID1", + SlaveIP: "10.10.10.10", + Name: "", + SlaveID: "", + State: "", + Statuses: []state.Status{{ + State: "a", + Healthy: Bool(true), + ContainerStatus: state.ContainerStatus{}, + }}, + DiscoveryInfo: state.DiscoveryInfo{ + Name: "name1", + Labels: struct { + Labels []state.Label "json:\"labels\"" + }{}, + Ports: state.Ports{DiscoveryPorts: []state.DiscoveryPort{ + {Protocol: "TCP", Number: 80, Name: "p"}, + {Protocol: "TCP", Number: 81, Name: "n"}}}}, + Labels: []state.Label{ + {Key: "foo", Value: "bar"}, + {Key: "fii", Value: "bar"}, + {Key: "fuu", Value: "bar"}}} + + assert.Equal(t, expected, result) } -func statuses(st ...state.Status) taskOpt { - return func(t *state.Task) { - t.Statuses = append(t.Statuses, st...) +func aTask(id string, ops ...func(*state.Task)) state.Task { + ts := &state.Task{ID: id} + for _, op := range ops { + op(ts) + } + return *ts +} + +func withIP(ip string) func(*state.Task) { + return func(task *state.Task) { + task.SlaveIP = ip } } -func discovery(dp state.DiscoveryInfo) taskOpt { - return func(t *state.Task) { - t.DiscoveryInfo = dp +func withInfo(name string, ops ...func(*state.DiscoveryInfo)) func(*state.Task) { + return func(task *state.Task) { + info := &state.DiscoveryInfo{Name: name} + for _, op := range ops { + op(info) + } + task.DiscoveryInfo = *info } } -func setLabels(kvs ...string) taskOpt { - return func(t *state.Task) { - if len(kvs)%2 != 0 { - panic("odd number") +func withPorts(ops ...func(port *state.DiscoveryPort)) func(*state.DiscoveryInfo) { + return func(info *state.DiscoveryInfo) { + var ports []state.DiscoveryPort + for _, op := range ops { + pt := &state.DiscoveryPort{} + op(pt) + ports = append(ports, *pt) } - for i := 0; i < len(kvs); i += 2 { - var label = state.Label{Key: kvs[i], Value: kvs[i+1]} - log.Debugf("Label1.1 : %v", label) - t.Labels = append(t.Labels, label) - log.Debugf("Label1.2 : %v", t.Labels) + info.Ports = state.Ports{ + DiscoveryPorts: ports, } - } } -func status(opts ...statusOpt) state.Status { - var s state.Status - for _, opt := range opts { - opt(&s) - } - return s -} - -func setDiscoveryPort(proto string, port int, name string) state.DiscoveryInfo { - - dp := state.DiscoveryPort{ - Protocol: proto, - Number: port, - Name: name, - } - - discoveryPorts := []state.DiscoveryPort{dp} - - ports := state.Ports{ - DiscoveryPorts: discoveryPorts, - } - - return state.DiscoveryInfo{ - Ports: ports, +func withPort(proto string, port int, name string) func(port *state.DiscoveryPort) { + return func(p *state.DiscoveryPort) { + p.Protocol = proto + p.Number = port + p.Name = name } } -func setDiscoveryPorts(proto1 string, port1 int, name1 string, proto2 string, port2 int, name2 string) state.DiscoveryInfo { +func withPortTCP(port int, name string) func(port *state.DiscoveryPort) { + return withPort("TCP", port, name) +} - dp1 := state.DiscoveryPort{ - Protocol: proto1, - Number: port1, - Name: name1, +func withStatus(ops ...func(*state.Status)) func(*state.Task) { + return func(task *state.Task) { + st := &state.Status{} + for _, op := range ops { + op(st) + } + task.Statuses = append(task.Statuses, *st) } - - dp2 := state.DiscoveryPort{ - Protocol: proto2, - Number: port2, - Name: name2, - } - - discoveryPorts := []state.DiscoveryPort{dp1, dp2} - - ports := state.Ports{ - DiscoveryPorts: discoveryPorts, - } - - return state.DiscoveryInfo{ - Ports: ports, +} +func withDefaultStatus(ops ...func(*state.Status)) func(*state.Task) { + return func(task *state.Task) { + for _, op := range ops { + st := &state.Status{ + State: "TASK_RUNNING", + Healthy: Bool(true), + } + op(st) + task.Statuses = append(task.Statuses, *st) + } } } -func setState(st string) statusOpt { - return func(s *state.Status) { - s.State = st +func withHealthy(st bool) func(*state.Status) { + return func(status *state.Status) { + status.Healthy = Bool(st) } } -func setHealthy(b bool) statusOpt { - return func(s *state.Status) { - s.Healthy = &b +func withState(st string) func(*state.Status) { + return func(status *state.Status) { + status.State = st } } + +func withLabel(key, value string) func(*state.Task) { + return func(task *state.Task) { + lbl := state.Label{Key: key, Value: value} + task.Labels = append(task.Labels, lbl) + } +} + +func Bool(v bool) *bool { + return &v +}