Divest api/ package of deps elsewhere in the nomad repo. (#5488)

* Divest api/ package of deps elsewhere in the nomad repo.

This will allow making api/ a module without then pulling in the
external repo, leading to a package name conflict.

This required some migration of tests to an apitests/ folder (can be
moved anywhere as it has no deps on it). It also required some
duplication of code, notably some test helpers from api/ -> apitests/
and part (but not all) of testutil/ -> api/testutil/.

Once there's more separation and an e.g. sdk/ folder those can be
removed in favor of a dep on the sdk/ folder, provided the sdk/ folder
doesn't depend on api/ or /.

* Also remove consul dep from api/ package

* Fix stupid linters

* Some restructuring
This commit is contained in:
Jeff Mitchell
2019-03-29 14:47:40 -04:00
committed by GitHub
parent 654c96bf10
commit 5676986196
27 changed files with 1311 additions and 376 deletions

View File

@@ -0,0 +1,56 @@
package apitests
import (
"testing"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/testutil"
)
type configCallback func(c *api.Config)
// seen is used to track which tests we have already marked as parallel
var seen map[*testing.T]struct{}
func init() {
seen = make(map[*testing.T]struct{})
}
func makeACLClient(t *testing.T, cb1 configCallback,
cb2 testutil.ServerConfigCallback) (*api.Client, *testutil.TestServer, *api.ACLToken) {
client, server := makeClient(t, cb1, func(c *testutil.TestServerConfig) {
c.ACL.Enabled = true
if cb2 != nil {
cb2(c)
}
})
// Get the root token
root, _, err := client.ACLTokens().Bootstrap(nil)
if err != nil {
t.Fatalf("failed to bootstrap ACLs: %v", err)
}
client.SetSecretID(root.SecretID)
return client, server, root
}
func makeClient(t *testing.T, cb1 configCallback,
cb2 testutil.ServerConfigCallback) (*api.Client, *testutil.TestServer) {
// Make client config
conf := api.DefaultConfig()
if cb1 != nil {
cb1(conf)
}
// Create server
server := testutil.NewTestServer(t, cb2)
conf.Address = "http://" + server.HTTPAddr
// Create client
client, err := api.NewClient(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
return client, server
}

View File

@@ -0,0 +1,85 @@
package apitests
import (
"testing"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/stretchr/testify/assert"
)
func TestJobs_Parse(t *testing.T) {
t.Parallel()
c, s := makeClient(t, nil, nil)
defer s.Stop()
jobs := c.Jobs()
checkJob := func(job *api.Job, expectedRegion string) {
if job == nil {
t.Fatal("job should not be nil")
}
region := job.Region
if region == nil {
if expectedRegion != "" {
t.Fatalf("expected job region to be '%s' but was unset", expectedRegion)
}
} else {
if expectedRegion != *region {
t.Fatalf("expected job region '%s', but got '%s'", expectedRegion, *region)
}
}
}
job, err := jobs.ParseHCL(mock.HCL(), true)
if err != nil {
t.Fatalf("err: %s", err)
}
checkJob(job, "global")
job, err = jobs.ParseHCL(mock.HCL(), false)
if err != nil {
t.Fatalf("err: %s", err)
}
checkJob(job, "")
}
func TestJobs_Summary_WithACL(t *testing.T) {
t.Parallel()
assert := assert.New(t)
c, s, root := makeACLClient(t, nil, nil)
defer s.Stop()
jobs := c.Jobs()
invalidToken := mock.ACLToken()
// Registering with an invalid token should fail
c.SetSecretID(invalidToken.SecretID)
job := testJob()
_, _, err := jobs.Register(job, nil)
assert.NotNil(err)
// Register with token should succeed
c.SetSecretID(root.SecretID)
resp2, wm, err := jobs.Register(job, nil)
assert.Nil(err)
assert.NotNil(resp2)
assert.NotEqual("", resp2.EvalID)
assertWriteMeta(t, wm)
// Query the job summary with an invalid token should fail
c.SetSecretID(invalidToken.SecretID)
result, _, err := jobs.Summary(*job.ID, nil)
assert.NotNil(err)
// Query the job summary with a valid token should succeed
c.SetSecretID(root.SecretID)
result, qm, err := jobs.Summary(*job.ID, nil)
assert.Nil(err)
assertQueryMeta(t, qm)
// Check that the result is what we expect
assert.Equal(*job.ID, result.JobID)
}

View File

@@ -0,0 +1,33 @@
package apitests
import (
"testing"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
func TestNodes_GC(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t, nil, nil)
defer s.Stop()
nodes := c.Nodes()
err := nodes.GC(uuid.Generate(), nil)
require.NotNil(err)
require.True(structs.IsErrUnknownNode(err))
}
func TestNodes_GcAlloc(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t, nil, nil)
defer s.Stop()
nodes := c.Nodes()
err := nodes.GcAlloc(uuid.Generate(), nil)
require.NotNil(err)
require.True(structs.IsErrUnknownAllocation(err))
}

View File

@@ -0,0 +1,101 @@
package apitests
import (
"testing"
"fmt"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
)
func TestAPI_OperatorAutopilotGetSetConfiguration(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t, nil, nil)
defer s.Stop()
operator := c.Operator()
var config *api.AutopilotConfiguration
retry.Run(t, func(r *retry.R) {
var err error
config, _, err = operator.AutopilotGetConfiguration(nil)
r.Check(err)
})
require.True(config.CleanupDeadServers)
// Change a config setting
newConf := &api.AutopilotConfiguration{CleanupDeadServers: false}
_, err := operator.AutopilotSetConfiguration(newConf, nil)
require.Nil(err)
config, _, err = operator.AutopilotGetConfiguration(nil)
require.Nil(err)
require.False(config.CleanupDeadServers)
}
func TestAPI_OperatorAutopilotCASConfiguration(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t, nil, nil)
defer s.Stop()
operator := c.Operator()
var config *api.AutopilotConfiguration
retry.Run(t, func(r *retry.R) {
var err error
config, _, err = operator.AutopilotGetConfiguration(nil)
r.Check(err)
})
require.True(config.CleanupDeadServers)
// Pass an invalid ModifyIndex
{
newConf := &api.AutopilotConfiguration{
CleanupDeadServers: false,
ModifyIndex: config.ModifyIndex - 1,
}
resp, _, err := operator.AutopilotCASConfiguration(newConf, nil)
require.Nil(err)
require.False(resp)
}
// Pass a valid ModifyIndex
{
newConf := &api.AutopilotConfiguration{
CleanupDeadServers: false,
ModifyIndex: config.ModifyIndex,
}
resp, _, err := operator.AutopilotCASConfiguration(newConf, nil)
require.Nil(err)
require.True(resp)
}
}
func TestAPI_OperatorAutopilotServerHealth(t *testing.T) {
t.Parallel()
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.Server.RaftProtocol = 3
})
defer s.Stop()
operator := c.Operator()
testutil.WaitForResult(func() (bool, error) {
out, _, err := operator.AutopilotServerHealth(nil)
if err != nil {
return false, err
}
if len(out.Servers) != 1 ||
!out.Servers[0].Healthy ||
out.Servers[0].Name != fmt.Sprintf("%s.global", s.Config.NodeName) {
return false, fmt.Errorf("%v", out)
}
return true, nil
}, func(err error) {
t.Fatalf("err: %v", err)
})
}

View File

@@ -0,0 +1,76 @@
package apitests
import (
"testing"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/nomad/api"
"github.com/stretchr/testify/require"
)
func TestAPI_OperatorSchedulerGetSetConfiguration(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t, nil, nil)
defer s.Stop()
operator := c.Operator()
var config *api.SchedulerConfigurationResponse
retry.Run(t, func(r *retry.R) {
var err error
config, _, err = operator.SchedulerGetConfiguration(nil)
r.Check(err)
})
require.True(config.SchedulerConfig.PreemptionConfig.SystemSchedulerEnabled)
// Change a config setting
newConf := &api.SchedulerConfiguration{PreemptionConfig: api.PreemptionConfig{SystemSchedulerEnabled: false}}
resp, wm, err := operator.SchedulerSetConfiguration(newConf, nil)
require.Nil(err)
require.NotZero(wm.LastIndex)
require.False(resp.Updated)
config, _, err = operator.SchedulerGetConfiguration(nil)
require.Nil(err)
require.False(config.SchedulerConfig.PreemptionConfig.SystemSchedulerEnabled)
}
func TestAPI_OperatorSchedulerCASConfiguration(t *testing.T) {
t.Parallel()
require := require.New(t)
c, s := makeClient(t, nil, nil)
defer s.Stop()
operator := c.Operator()
var config *api.SchedulerConfigurationResponse
retry.Run(t, func(r *retry.R) {
var err error
config, _, err = operator.SchedulerGetConfiguration(nil)
r.Check(err)
})
require.True(config.SchedulerConfig.PreemptionConfig.SystemSchedulerEnabled)
// Pass an invalid ModifyIndex
{
newConf := &api.SchedulerConfiguration{
PreemptionConfig: api.PreemptionConfig{SystemSchedulerEnabled: false},
ModifyIndex: config.SchedulerConfig.ModifyIndex - 1,
}
resp, wm, err := operator.SchedulerCASConfiguration(newConf, nil)
require.Nil(err)
require.NotZero(wm.LastIndex)
require.False(resp.Updated)
}
// Pass a valid ModifyIndex
{
newConf := &api.SchedulerConfiguration{
PreemptionConfig: api.PreemptionConfig{SystemSchedulerEnabled: false},
ModifyIndex: config.SchedulerConfig.ModifyIndex,
}
resp, wm, err := operator.SchedulerCASConfiguration(newConf, nil)
require.Nil(err)
require.NotZero(wm.LastIndex)
require.True(resp.Updated)
}
}

View File

@@ -0,0 +1,102 @@
package apitests
import (
"encoding/json"
"testing"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
// Tests that api and struct values are equivalent
//
// Given that vendoring libraries prune tests by default, test dependencies
// aren't leaked to clients of the package - so it should be safe to add
// such dependency without affecting api clients.
func TestDefaultResourcesAreInSync(t *testing.T) {
apiR := api.DefaultResources()
structsR := structs.DefaultResources()
require.EqualValues(t, *structsR, toStructsResource(t, apiR))
// match after canonicalization
apiR.Canonicalize()
structsR.Canonicalize()
require.EqualValues(t, *structsR, toStructsResource(t, apiR))
}
func TestMinResourcesAreInSync(t *testing.T) {
apiR := api.MinResources()
structsR := structs.MinResources()
require.EqualValues(t, *structsR, toStructsResource(t, apiR))
// match after canonicalization
apiR.Canonicalize()
structsR.Canonicalize()
require.EqualValues(t, *structsR, toStructsResource(t, apiR))
}
func TestNewDefaultRescheulePolicyInSync(t *testing.T) {
cases := []struct {
typ string
expected structs.ReschedulePolicy
}{
{"service", structs.DefaultServiceJobReschedulePolicy},
{"batch", structs.DefaultBatchJobReschedulePolicy},
{"system", structs.ReschedulePolicy{}},
}
for _, c := range cases {
t.Run(c.typ, func(t *testing.T) {
apiP := api.NewDefaultReschedulePolicy(c.typ)
var found structs.ReschedulePolicy
toStructs(t, &found, apiP)
require.EqualValues(t, c.expected, found)
})
}
}
func TestNewDefaultRestartPolicyInSync(t *testing.T) {
cases := []struct {
typ string
expected structs.RestartPolicy
}{
{"service", structs.DefaultServiceJobRestartPolicy},
{"batch", structs.DefaultBatchJobRestartPolicy},
{"system", structs.DefaultServiceJobRestartPolicy},
}
for _, c := range cases {
t.Run(c.typ, func(t *testing.T) {
job := api.Job{Type: &c.typ}
var tg api.TaskGroup
tg.Canonicalize(&job)
apiP := tg.RestartPolicy
var found structs.RestartPolicy
toStructs(t, &found, apiP)
require.EqualValues(t, c.expected, found)
})
}
}
func toStructsResource(t *testing.T, in *api.Resources) structs.Resources {
var out structs.Resources
toStructs(t, &out, in)
return out
}
func toStructs(t *testing.T, out, in interface{}) {
bytes, err := json.Marshal(in)
require.NoError(t, err)
err = json.Unmarshal(bytes, &out)
require.NoError(t, err)
}

View File

@@ -0,0 +1,171 @@
package apitests
import (
"testing"
"time"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/assert"
)
// Verifies that reschedule policy is merged correctly
func TestTaskGroup_Canonicalize_ReschedulePolicy(t *testing.T) {
type testCase struct {
desc string
jobReschedulePolicy *api.ReschedulePolicy
taskReschedulePolicy *api.ReschedulePolicy
expected *api.ReschedulePolicy
}
testCases := []testCase{
{
desc: "Default",
jobReschedulePolicy: nil,
taskReschedulePolicy: nil,
expected: &api.ReschedulePolicy{
Attempts: intToPtr(structs.DefaultBatchJobReschedulePolicy.Attempts),
Interval: timeToPtr(structs.DefaultBatchJobReschedulePolicy.Interval),
Delay: timeToPtr(structs.DefaultBatchJobReschedulePolicy.Delay),
DelayFunction: stringToPtr(structs.DefaultBatchJobReschedulePolicy.DelayFunction),
MaxDelay: timeToPtr(structs.DefaultBatchJobReschedulePolicy.MaxDelay),
Unlimited: boolToPtr(structs.DefaultBatchJobReschedulePolicy.Unlimited),
},
},
{
desc: "Empty job reschedule policy",
jobReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(0),
Interval: timeToPtr(0),
Delay: timeToPtr(0),
MaxDelay: timeToPtr(0),
DelayFunction: stringToPtr(""),
Unlimited: boolToPtr(false),
},
taskReschedulePolicy: nil,
expected: &api.ReschedulePolicy{
Attempts: intToPtr(0),
Interval: timeToPtr(0),
Delay: timeToPtr(0),
MaxDelay: timeToPtr(0),
DelayFunction: stringToPtr(""),
Unlimited: boolToPtr(false),
},
},
{
desc: "Inherit from job",
jobReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(1),
Interval: timeToPtr(20 * time.Second),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(10 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
taskReschedulePolicy: nil,
expected: &api.ReschedulePolicy{
Attempts: intToPtr(1),
Interval: timeToPtr(20 * time.Second),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(10 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
},
{
desc: "Set in task",
jobReschedulePolicy: nil,
taskReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(5),
Interval: timeToPtr(2 * time.Minute),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(10 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
expected: &api.ReschedulePolicy{
Attempts: intToPtr(5),
Interval: timeToPtr(2 * time.Minute),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(10 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
},
{
desc: "Merge from job",
jobReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(1),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(10 * time.Minute),
},
taskReschedulePolicy: &api.ReschedulePolicy{
Interval: timeToPtr(5 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
expected: &api.ReschedulePolicy{
Attempts: intToPtr(1),
Interval: timeToPtr(5 * time.Minute),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(10 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
},
{
desc: "Override from group",
jobReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(1),
MaxDelay: timeToPtr(10 * time.Second),
},
taskReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(5),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(20 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
expected: &api.ReschedulePolicy{
Attempts: intToPtr(5),
Interval: timeToPtr(structs.DefaultBatchJobReschedulePolicy.Interval),
Delay: timeToPtr(20 * time.Second),
MaxDelay: timeToPtr(20 * time.Minute),
DelayFunction: stringToPtr("constant"),
Unlimited: boolToPtr(false),
},
},
{
desc: "Attempts from job, default interval",
jobReschedulePolicy: &api.ReschedulePolicy{
Attempts: intToPtr(1),
},
taskReschedulePolicy: nil,
expected: &api.ReschedulePolicy{
Attempts: intToPtr(1),
Interval: timeToPtr(structs.DefaultBatchJobReschedulePolicy.Interval),
Delay: timeToPtr(structs.DefaultBatchJobReschedulePolicy.Delay),
DelayFunction: stringToPtr(structs.DefaultBatchJobReschedulePolicy.DelayFunction),
MaxDelay: timeToPtr(structs.DefaultBatchJobReschedulePolicy.MaxDelay),
Unlimited: boolToPtr(structs.DefaultBatchJobReschedulePolicy.Unlimited),
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
job := &api.Job{
ID: stringToPtr("test"),
Reschedule: tc.jobReschedulePolicy,
Type: stringToPtr(api.JobTypeBatch),
}
job.Canonicalize()
tg := &api.TaskGroup{
Name: stringToPtr("foo"),
ReschedulePolicy: tc.taskReschedulePolicy,
}
tg.Canonicalize(job)
assert.Equal(t, tc.expected, tg.ReschedulePolicy)
})
}
}

View File

@@ -0,0 +1,83 @@
package apitests
import (
"testing"
"time"
"github.com/hashicorp/nomad/api"
)
// boolToPtr returns the pointer to a boolean
func boolToPtr(b bool) *bool {
return &b
}
// intToPtr returns the pointer to an int
func intToPtr(i int) *int {
return &i
}
// timeToPtr returns the pointer to a time stamp
func timeToPtr(t time.Duration) *time.Duration {
return &t
}
// stringToPtr returns the pointer to a string
func stringToPtr(str string) *string {
return &str
}
func assertQueryMeta(t *testing.T, qm *api.QueryMeta) {
t.Helper()
if qm.LastIndex == 0 {
t.Fatalf("bad index: %d", qm.LastIndex)
}
if !qm.KnownLeader {
t.Fatalf("expected known leader, got none")
}
}
func assertWriteMeta(t *testing.T, wm *api.WriteMeta) {
t.Helper()
if wm.LastIndex == 0 {
t.Fatalf("bad index: %d", wm.LastIndex)
}
}
func testJob() *api.Job {
task := api.NewTask("task1", "exec").
SetConfig("command", "/bin/sleep").
Require(&api.Resources{
CPU: intToPtr(100),
MemoryMB: intToPtr(256),
}).
SetLogConfig(&api.LogConfig{
MaxFiles: intToPtr(1),
MaxFileSizeMB: intToPtr(2),
})
group := api.NewTaskGroup("group1", 1).
AddTask(task).
RequireDisk(&api.EphemeralDisk{
SizeMB: intToPtr(25),
})
job := api.NewBatchJob("job1", "redis", "region1", 1).
AddDatacenter("dc1").
AddTaskGroup(group)
return job
}
// conversions utils only used for testing
// added here to avoid linter warning
// int64ToPtr returns the pointer to an int
func int64ToPtr(i int64) *int64 {
return &i
}
// float64ToPtr returns the pointer to an float64
func float64ToPtr(f float64) *float64 {
return &f
}